<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Joe Buckle</title>
    <description>The latest articles on DEV Community by Joe Buckle (@joebuckle-dev).</description>
    <link>https://dev.to/joebuckle-dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F266265%2F4888d4ab-b03b-4d99-b4e1-fadc3cd6bc05.jpeg</url>
      <title>DEV Community: Joe Buckle</title>
      <link>https://dev.to/joebuckle-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joebuckle-dev"/>
    <language>en</language>
    <item>
      <title>Hardening an AI coding agent: the failures, and the code that fixed them</title>
      <dc:creator>Joe Buckle</dc:creator>
      <pubDate>Fri, 31 Jul 2026 11:33:04 +0000</pubDate>
      <link>https://dev.to/joebuckle-dev/hardening-an-ai-coding-agent-the-failures-and-the-code-that-fixed-them-g3c</link>
      <guid>https://dev.to/joebuckle-dev/hardening-an-ai-coding-agent-the-failures-and-the-code-that-fixed-them-g3c</guid>
      <description>&lt;p&gt;At &lt;a href="https://univoco.io" rel="noopener noreferrer"&gt;Univoco&lt;/a&gt; we build retrieval-augmented assistants over a customer's own documentation. One of them is a coding agent that writes code for a proprietary document layout engine.&lt;/p&gt;

&lt;p&gt;The useful thing about that engine is that there is effectively nothing about it on the public internet. The model cannot have learned it. Everything the agent knows comes from a private documentation index and the tools we give it to search that index.&lt;/p&gt;

&lt;p&gt;That makes it an unusually clean laboratory. When memorisation is off the table, you get to watch the problem-solving process itself, and watch it fail.&lt;/p&gt;

&lt;p&gt;It failed a lot, and the failures were rarely the ones I expected. What follows is the list, in the order we hit them, with the code that fixed each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contents
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Failure&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Search tools are literal&lt;/td&gt;
&lt;td&gt;filename matching, construct footprints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;It searches the same thing forever&lt;/td&gt;
&lt;td&gt;five layers of loop detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;One phrasing is a single point of failure&lt;/td&gt;
&lt;td&gt;query inflation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Goal words vs. index words&lt;/td&gt;
&lt;td&gt;a deterministic phrasebook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;The retrieval tool invented APIs&lt;/td&gt;
&lt;td&gt;self-auditing lookups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Deciding an API is invented&lt;/td&gt;
&lt;td&gt;the checker stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Findings get ignored&lt;/td&gt;
&lt;td&gt;finish gates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;The wording of a correction is load-bearing&lt;/td&gt;
&lt;td&gt;the envelope and the silence trailer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;A deletion that grew the file&lt;/td&gt;
&lt;td&gt;take the generation out&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  1. Search tools are literal
&lt;/h2&gt;

&lt;p&gt;The standard shape is fetch-then-answer: embed the question, pull the top-k chunks, hand them to the model. It works for a great many tasks, and it has one structural problem. The folder gets assembled before anyone has understood the problem, so you are deciding what &lt;em&gt;might&lt;/em&gt; be useful before you know what is &lt;em&gt;needed&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Giving the agent search tools of its own fixes the ordering and immediately produces a different class of bug, because search tools are literal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content search cannot see filenames
&lt;/h3&gt;

&lt;p&gt;The agent searched every file in the project for &lt;code&gt;json&lt;/code&gt;, found nothing, and offered to create a sample data file. &lt;code&gt;sampleData.json&lt;/code&gt; was sitting in the project. The word it needed was on the front of the folder, not inside it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;project_paths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iter_project_files&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# A model hunting "the json"/"the xml" searches by TYPE, and content search can
&lt;/span&gt;    &lt;span class="c1"&gt;# never find a file whose content doesn't mention its own format (live failure:
&lt;/span&gt;    &lt;span class="c1"&gt;# sampleData.json was invisible to pattern "json" and the agent offered to create
&lt;/span&gt;    &lt;span class="c1"&gt;# a file that existed). Names are part of the searchable surface.
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="n"&gt;name_hits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (filename match)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A grep hit is a line, but the thing you edit is a construct
&lt;/h3&gt;

&lt;p&gt;Asked to remove a feature, the agent searched, found the one matching line, deleted it, and declared success. The five lines above it declared and configured the same object.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;find_in_files&lt;/code&gt; now returns grep-style context plus an automatic &lt;strong&gt;variable footprint&lt;/strong&gt;. A hit on &lt;code&gt;layout.blockStart(box)&lt;/code&gt; also lists &lt;code&gt;var box = new Block()&lt;/code&gt; and every property line of that variable. Only declared variables expand, so globals like &lt;code&gt;layout&lt;/code&gt; never blow the output up.&lt;/p&gt;

&lt;p&gt;The rule it encodes: &lt;em&gt;the construct is all of its lines, not the one that matched.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Neither fix is clever. Both exist because something shipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two layers of tools
&lt;/h3&gt;

&lt;p&gt;There are two layers of tools, and the split explains several of the fixes that follow.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;base tools&lt;/strong&gt; ship with the platform and are deliberately generic. &lt;code&gt;list_files&lt;/code&gt;, &lt;code&gt;read_file&lt;/code&gt;, &lt;code&gt;find_in_files&lt;/code&gt;, &lt;code&gt;write_file&lt;/code&gt;, &lt;code&gt;edit_file&lt;/code&gt;, &lt;code&gt;apply_edit&lt;/code&gt;, &lt;code&gt;delete_lines&lt;/code&gt;: every customer gets them, and not one of them knows anything about anybody's domain.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;client tools&lt;/strong&gt; are authored per customer and live in that customer's own directory. Here that means &lt;code&gt;lookup_docs&lt;/code&gt;, &lt;code&gt;resolve_topic&lt;/code&gt;, &lt;code&gt;find_construct&lt;/code&gt;, &lt;code&gt;check_symbols&lt;/code&gt; and &lt;code&gt;dry_run&lt;/code&gt;, none of which mean anything without this engine's documentation and this engine's object model.&lt;/p&gt;

&lt;p&gt;The mechanic that matters is that &lt;strong&gt;a client tool shadows a base tool of the same name&lt;/strong&gt;. Two do. The client &lt;code&gt;find_in_files&lt;/code&gt; adds something the generic one could not: when a literal search finds nothing, concept words like "block" or "the box" expand to the engine's real symbols, so the agent never has to translate a concept into code before it can search for it. And &lt;code&gt;rag_lookup&lt;/code&gt;, the knowledge lookup itself, is shadowed too, which is what makes the fix in section 5 possible.&lt;/p&gt;

&lt;p&gt;That split is the architecture. The generic layer is table stakes and everyone has it. The layer that knows what a "block" is called in this customer's world is the part that cannot be bought.&lt;/p&gt;

&lt;h3&gt;
  
  
  The two knowledge tools
&lt;/h3&gt;

&lt;p&gt;These two need separating, because the gap between them drives a lot of what follows.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;What it costs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;lookup_docs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;full-text search over the indexed documentation, returning the chunks that literally contain the term. No embeddings, no model, no generation.&lt;/td&gt;
&lt;td&gt;milliseconds, and it cannot invent anything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rag_lookup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;semantic retrieval, then a model composes an answer out of whatever came back&lt;/td&gt;
&lt;td&gt;ten to thirty seconds, and the composing step can invent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second one is what most people mean by RAG: retrieve some relevant text, hand it to a model, let the model write the answer. It is the right tool for "how do I build a table", and the wrong tool for "does &lt;code&gt;tableColumnRule&lt;/code&gt; exist", which is the distinction the agent is told to route on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TWO knowledge tools, and the choice matters. &lt;code&gt;lookup_docs&lt;/code&gt; is ~1000x cheaper. Naming a symbol you already know is a FACT CHECK, not a research question.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That framing looks like a cost optimisation. It turns out to be a correctness one, for reasons section 5 gets to.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. It searches the same thing forever
&lt;/h2&gt;

&lt;p&gt;My favourite failure of the whole project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The agent ran the same knowledge lookup &lt;strong&gt;twelve times in one turn&lt;/strong&gt;, identical arguments each time, and the results themselves contained notes pointing out it had already asked. It made no difference.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The variant version was worse, and no exact-match check can catch it, because the arguments differ every time. One table turn ran &lt;strong&gt;12 steps and 170k input tokens&lt;/strong&gt;, and three of its five knowledge lookups re-confirmed the same three API names. Those names were already in its context, listed in the prompt fragment it had been handed at the start of the turn.&lt;/p&gt;

&lt;p&gt;It was not searching. It was checking something it already knew, at ten to thirty seconds a time. The operator's note in the ledger puts it better than I can:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;once it knows how to do stuff it shouldn't need to keep trying to find it at the rag end&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A human has an internal sense of going in circles. The model does not, and telling it so in the tool output does nothing, because that text is just more context. Worse, on a provider without prompt caching the fixed ~11k of system prompt and tool schemas is re-sent every step, so cost is roughly fixed times steps plus transcript. Fewer steps is the only real lever, and a redundant lookup adds steps.&lt;/p&gt;

&lt;p&gt;The fix is five layers, and &lt;strong&gt;the layering is the point&lt;/strong&gt;: each layer only catches what the one before it structurally cannot see.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: identical calls
&lt;/h3&gt;

&lt;p&gt;A counter in the tool loop, tripping at &lt;code&gt;_MAX_IDENTICAL_CALLS = 4&lt;/code&gt;. Past that the call is not executed, but it still has to be answered, because every tool call in the transcript needs a result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Stuck-loop detector: the same call over and over is fixation, not progress.
# Answer it with a stub (every tool_call must be answered) and head for the
# forced final answer instead of burning the rest of the budget.
&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;call_counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call_counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;call_counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_MAX_IDENTICAL_CALLS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;stuck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_call_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[not run - you have made this exact call &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;call_counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; times this turn]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Layer 2: rewordings within a turn
&lt;/h3&gt;

&lt;p&gt;Reworded questions are distinct argument strings, so the identical-call counter never fires on them. Catching those needs a budget rather than a counter:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Constant&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_MAX_LOOKUPS_PER_TURN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;12&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;live lookups across all wordings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;_MAX_LIVE_LOOKUPS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;per identical query, then the turn cache answers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Layer 3: rewordings across turns and sessions
&lt;/h3&gt;

&lt;p&gt;This one I like, because the obvious solution is wrong. You could embed every query and compare vectors. We do not, because that is a per-probe cost on the hot path for a problem that plain string handling solves.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Matching is deliberately embedding-free: normalise (lowercase, drop stopwords, light stemming with a double-consonant collapse so splitting to split), token-sort, then exact key hit or a &lt;code&gt;difflib&lt;/code&gt; close match. Deterministic, stdlib, zero per-probe cost, and it catches exactly the reworded-not-rethought queries above. Genuinely different questions stay distinct.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The similarity threshold is &lt;code&gt;0.8&lt;/code&gt;, and the calibration comment is the reason I trust it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the live pair whose only difference was the domain word scores &lt;strong&gt;0.833&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;two genuinely different questions, &lt;code&gt;create table&lt;/code&gt; vs &lt;code&gt;create box&lt;/code&gt;, score about &lt;strong&gt;0.73&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The window between them is the sweet spot, and it was measured rather than guessed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 4: concurrent duplicates
&lt;/h3&gt;

&lt;p&gt;Read-only calls get dispatched together, so three rewordings can all be in flight before any of them returns, and a dedupe that runs on completion never sees them. That needs a leader/follower collapse on the normalised query: the first runs, the rest wait and receive its answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 5: do not ask for what you were already given
&lt;/h3&gt;

&lt;p&gt;None of the above touches the failure this section opened with. A budget caps how much re-asking costs, but the agent re-confirming names it was handed at turn start should not be asking at all.&lt;/p&gt;

&lt;p&gt;Writes to domain code are normally refused unless a knowledge lookup has run in that turn, which is what forces grounding. That gate was also what forced the redundant lookups: the agent had the names, but the rule did not know that.&lt;/p&gt;

&lt;p&gt;So the prompt fragments now declare what they supply. Each construct fragment carries a &lt;code&gt;provides&lt;/code&gt; list, generated rather than hand-written and scoped to one family, so the table fragment declares the table companions and not the rule ones. The task brief unions those into &lt;code&gt;provided_symbols&lt;/code&gt;, and the gate exempts a write whose &lt;code&gt;new X()&lt;/code&gt; constructors are all covered. A write reaching for a construct that was never delivered still grounds, and &lt;code&gt;check_symbols&lt;/code&gt; remains the backstop before anything lands.&lt;/p&gt;

&lt;p&gt;The general shape: a rule that forces good behaviour will also force pointless behaviour unless it can see what has already been satisfied.&lt;/p&gt;

&lt;h3&gt;
  
  
  The counter-lesson
&lt;/h3&gt;

&lt;p&gt;This was the most useful thing in the section. The budget once cut a model off &lt;strong&gt;mid-way through correctly verifying eleven symbols&lt;/strong&gt; it was about to use. It was doing exactly the right thing and we throttled it.&lt;/p&gt;

&lt;p&gt;So budget is now charged per tool rather than per call:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;_MAX_LOOKUPS_PER_TURN&lt;/code&gt; caps LIVE lookups across all wordings; cached serves and cheap tools are free (&lt;code&gt;projects.budget_tools&lt;/code&gt;, default &lt;code&gt;rag_lookup&lt;/code&gt;): a millisecond &lt;code&gt;lookup_docs&lt;/code&gt; fact-check per symbol is diligence we demand, not cost to ration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;If your rate limit punishes the behaviour you are trying to encourage, the limit is wrong, not the behaviour.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One last piece of absurdity, included because it is a genuine class of bug and not just a funny story. An earlier nudge injected into the tool results said &lt;em&gt;"look up the interface's REAL members"&lt;/em&gt;. The model ran that sentence, verbatim, as a semantic search query. Twelve times.&lt;/p&gt;

&lt;p&gt;Nudges now name an action, and only ever suggest exact-name lookups, never a phrase, because anything phrase-shaped in an agent's context is a candidate query.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Query inflation
&lt;/h2&gt;

&lt;p&gt;One user phrasing is a single point of failure for recall. If the question happens to be worded unlike the corpus, retrieval returns nothing useful and the agent concludes the knowledge base is empty rather than that it asked badly.&lt;/p&gt;

&lt;p&gt;So before the lookups run, a forced tool call reformulates the question into several deliberately different queries. Not paraphrases: different &lt;strong&gt;angles&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_QUERIES_TOOL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_queries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reformulate the question into optimised knowledge-base search queries using the right &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domain keywords / API terms. For a how-to / implementation / &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;how do I ...&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; question, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;return 2-3 queries covering different angles (e.g. the core API/object, and a usage &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example). Return a single query ONLY for a trivial factual lookup (e.g. &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;what is X&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;).&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user message that drives it is where the actual behaviour lives. Almost every clause is there to stop a specific degenerate output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate {max_queries} search queries covering DIFFERENT angles so retrieval is thorough:
1. A focused how-to query for the specific task.
2. A BROAD keyword query - just the core nouns / concepts / terms (e.g.
   'user login session token'), to surface related material the specific query might miss.
3. If more are allowed, add a query aimed at a concrete example.
Do NOT return only a verbatim copy of the question. Only a trivial factual lookup gets a
single query.

Examples (apply the SAME pattern to the question's own domain):
- 'set up user authentication' -&amp;gt; ['how to set up user authentication',
  'authentication login session token configuration']
- 'export a report to CSV' -&amp;gt; ['how to export a report to CSV',
  'CSV export columns rows formatting']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system line is one sentence and does most of the work:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You generate DISTINCT knowledge-base search queries using precise keywords / API terms. Never echo the question verbatim.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without it you get the question back with the word order shuffled, which is the reworded-not-rethought problem from section 2, except now we are paying to generate it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three details that are not obvious
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Each query carries a human label.&lt;/strong&gt; The tool schema requires an &lt;code&gt;about&lt;/code&gt; field alongside the query: &lt;em&gt;"a short, natural human phrase for what this looks up, e.g. 'creating tables with borders'"&lt;/em&gt;. That is not for retrieval, it is for the activity line the user watches. Generating the explanation in the same call as the query is free; deriving it afterwards is another model call or a bad heuristic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The queries are not fanned out by code.&lt;/strong&gt; They are injected back into the prompt as an instruction, so the agent still owns sequencing and can stop early.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_guided_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query_objs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adaptive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Wrap a question with guidance to search each expanded query, then answer (or dig deeper).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;qlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;query_objs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_ADAPTIVE_TAIL&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;adaptive&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; Then answer fully.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;[Search the knowledge base for: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;qlist&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expansion without a brake is just a licence to over-search&lt;/strong&gt;, which is section 2 all over again. So &lt;code&gt;_ADAPTIVE_TAIL&lt;/code&gt; ships with it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Then judge the results: if they already answer the question, STOP and answer now, do not run extra, redundant, or tangential searches. Only if a genuine gap remains (or the results reference something specific you still need) search again for exactly that. Aim for the fewest searches that fully cover the question.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is a ceiling of &lt;code&gt;6&lt;/code&gt; regardless of configuration, and &lt;code&gt;max_queries: -1&lt;/code&gt; means the model decides within that ceiling.&lt;/p&gt;

&lt;p&gt;One clarification, since the two get conflated: this is multi-query rewriting, &lt;strong&gt;not HyDE&lt;/strong&gt;. No hypothetical answer document is generated. In a domain the model has never seen, a hypothetical document is a hallucination with extra steps, and embedding it just retrieves whatever is nearest to the invention.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The agent's words are not the index's words
&lt;/h2&gt;

&lt;p&gt;This is the failure that cost the most and looked the least like a bug.&lt;/p&gt;

&lt;p&gt;The agent phrases things by goal: &lt;em&gt;"display the data in a table"&lt;/em&gt;, &lt;em&gt;"put the XML in a table"&lt;/em&gt;. The corpus is indexed by the engine's own vocabulary: &lt;code&gt;openStream&lt;/code&gt;, &lt;code&gt;xmlDoc&lt;/code&gt;, &lt;code&gt;selectNodes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To a human expert those are obviously the same topic. To a retriever they have almost nothing in common, so the lookups returned nothing, the agent concluded the capability did not exist, and it reached for browser JavaScript instead.&lt;/p&gt;

&lt;p&gt;Missing real content &lt;strong&gt;and&lt;/strong&gt; inventing names is the expensive combination, because the invention then looks like a hallucination and you go and debug the wrong component.&lt;/p&gt;

&lt;p&gt;Three layers, in increasing order of force.&lt;/p&gt;

&lt;h3&gt;
  
  
  A deterministic phrasebook
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;resolve_topic&lt;/code&gt; maps a goal to the real symbols, with no model involved at all:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The agent phrases requests by goal; the docs are indexed by object-model vocabulary. That mismatch made it MISS real content and INVENT names, spiralling. This reads the terminology datagraph (&lt;code&gt;_topics.json&lt;/code&gt;) and hands back the real symbols, a note on the pattern, and the exact next lookup, so the agent searches truth instead of guessing.&lt;/p&gt;

&lt;p&gt;Zero LLM, deterministic. Call it BEFORE &lt;code&gt;rag_lookup&lt;/code&gt; when you don't already know the API names.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The map is mined rather than hand-written:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;human phrasings come from query-pattern tags on the worked examples&lt;/li&gt;
&lt;li&gt;symbols come from a regex over the example code, validated against the authoritative API dictionary&lt;/li&gt;
&lt;li&gt;a curated overlay adds goal aliases for the high-value topics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Matching is stopword-filtered token overlap ranked by hit count. No embeddings anywhere in it.&lt;/p&gt;

&lt;h3&gt;
  
  
  A confidence ladder
&lt;/h3&gt;

&lt;p&gt;Because a phrasebook that is confidently wrong is worse than none. The first version applied full force to any match at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Confidence ladder (live miss: 'footer with page numbers' shared ONE word with the
# layers topic and got the full-strength 'do NOT invent' route, walling the agent off
# from the real constructs). A weak match is a HINT, not a law: strict only when the
# route explains at least half the goal's significant words, or 2+ of them.
&lt;/span&gt;&lt;span class="n"&gt;coverage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;coverage&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WEAK MATCH - the goal only loosely fits a mapped topic; treat this as a &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hint, NOT the answer:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A guardrail with a false positive does not fail neutrally. It actively steers the agent away from the answer, and it does it with authority.&lt;/p&gt;

&lt;h3&gt;
  
  
  A de-nouning rewrite
&lt;/h3&gt;

&lt;p&gt;Users phrase goals in their own nouns (&lt;em&gt;"display parts from an XML file grouped by category"&lt;/em&gt;) and documentation indexes mechanisms (&lt;em&gt;"load an XML file, iterate elements into table rows, heading per group"&lt;/em&gt;). One cheap call normalises the retrieval query before the lookup stack sees it, layered outermost so that dedupe and the persistent cache both key on the normalised form and hit more often.&lt;/p&gt;

&lt;p&gt;The constraint on that rewriter is the interesting part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#: Operations only, never API names: the rewriter half-knows jargon, and an invented
#: symbol in the query would poison retrieval with exactly the hallucination the
#: lookup exists to prevent. API discovery stays retrieval's job.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It ships disabled, incidentally. The doctrine version in the prompt does most of the work and the mechanical version is held in reserve for when doctrine leaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where the phrasebook is applied matters more than the phrasebook.&lt;/strong&gt; The terminology hint is prepended to every lookup result, whether or not the model asked for it, and whether or not retrieval returned anything, because a gap is exactly when the real names matter most.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The tool was the liar
&lt;/h2&gt;

&lt;p&gt;The agent kept writing invented API calls into its work. It would be caught, it would look the correct ones up, and then it would do it again. Seven rounds of this in one session.&lt;/p&gt;

&lt;p&gt;I assumed the model was inventing them. That is the famous failure mode and I never questioned it.&lt;/p&gt;

&lt;p&gt;Then I read what the lookup tool had actually returned, rather than the model's account of what it returned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The retrieval pipeline was generating the invented APIs.&lt;/strong&gt; The style that answers conceptual questions composes an answer from retrieved examples, and that generation step had invented &lt;code&gt;layout.tableCellEnd&lt;/code&gt; and &lt;code&gt;Table.cellSpacing&lt;/code&gt;, neither of which exists, while asserting they came &lt;em&gt;"directly from the example in the provided context"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That is not really a bug, it is what generation does. Ask an obliging model for a worked example and it will produce a worked example, and where the retrieved material runs thin it fills the gap with the most plausible name available. The word "lookup" was what fooled me: I had been treating an endpoint that ends in a generate step as though it returned facts, when the only thing separating it from the agent was that its inventions arrived with a citation attached.&lt;/p&gt;

&lt;p&gt;The model was faithfully copying bad information from the one source it had every reason to trust. Worse, the loop was closed: the check flagged the file, the agent re-ran the same lookup to find the right answer, and got the same poison back.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The debugging rule that came out of it&lt;/strong&gt;, because I lost days to not knowing it: read the actual tool results in the log, not the model's narration of them. A large proportion of what looked like model failures turned out to be a tool feeding it poison.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The fix is a client-side shadow that wraps the built-in lookup and audits its output with the same symbol checker the write path uses:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The examples style GENERATES its answers, and generation can invent APIs. A live run returned examples using APIs that do not exist, the agent wrote them, &lt;code&gt;check_symbols&lt;/code&gt; flagged the file, the agent re-ran the same lookup and got the same poison: a perfect loop. This shadow breaks it at the source: run the built-in lookup, then run the SAME symbol audit the write path uses over the returned example, and hand the agent the answer and the corrections in one result, so it learns the example is partly wrong BEFORE writing it, together with the interface's real members to build from instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The second-order bug, which is the one worth stealing
&lt;/h3&gt;

&lt;p&gt;The lookup cache had pickled the poison. Of &lt;strong&gt;137 cached entries, 126 carried warning text&lt;/strong&gt;. Every fuzzy cache hit was re-serving a known-bad answer, forever.&lt;/p&gt;

&lt;p&gt;Flagged answers are now uncacheable, and the cache module has a rule list at the top that reads like scar tissue, because it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;never cache failures or empties, because those are the answers most legitimately retried, and a wrong fuzzy hit on a cached "returned nothing" is poison&lt;/li&gt;
&lt;li&gt;serve each entry at most once per turn, because a fuzzy match can be wrong and the corpus can change&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  One more thing, and I think it generalises
&lt;/h3&gt;

&lt;p&gt;Once the guardrails were in, we probed the same grounded prompt across three model tiers, all with identical retrieval and identical checks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model tier&lt;/th&gt;
&lt;th&gt;Grounded probe&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;frontier&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;5/5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mid&lt;/td&gt;
&lt;td&gt;2/5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cheap&lt;/td&gt;
&lt;td&gt;0/5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The verdict in the notes: &lt;em&gt;the guardrails make a weak model SAFE (bounced), not GROUNDED.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A gate stack stops bad output reaching the user. It does not turn a model that cannot follow grounding instructions into one that can. Budget for the model tier; do not expect the scaffolding to buy it back.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Deciding that an API is invented
&lt;/h2&gt;

&lt;p&gt;This is the check everything else depends on, and the first version was wrong in an instructive way. It asked an LLM to list the symbols in the agent's code, then full-text searched each one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every step leaned on the thing that was broken: an LLM summarising another LLM's hallucination, and a search whose only possible verdict was "not found", which tells an agent nothing about what to write instead, so it kept the invented call.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That second clause is the part I underestimated. &lt;strong&gt;"Not found" is not actionable.&lt;/strong&gt; An agent that receives it has no better move available than the one it already made, so it makes it again.&lt;/p&gt;

&lt;p&gt;The reframe: the documentation is complete and enumerable, so the question is &lt;em&gt;decidable&lt;/em&gt;. Resolve what an object is, then ask whether the member exists on it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;layout.tableCellEnd                       -&amp;gt;  layout is Layout    -&amp;gt;  no such member  -&amp;gt;  INVENTED
var cell = new TableCell(); cell.start()  -&amp;gt;  cell is TableCell   -&amp;gt;  check `start`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Where the index comes from
&lt;/h3&gt;

&lt;p&gt;"Complete and enumerable" is carrying a lot of weight in that paragraph, so here is where the index actually comes from. It is not hand-written, and that is the only reason the verdict is trustworthy. A hand-maintained list of what exists is just one more thing that can drift from what exists.&lt;/p&gt;

&lt;p&gt;The member index falls out of the documentation's own structure. Every documented member is its own chunk, titled &lt;code&gt;Interface-member&lt;/code&gt;, so parsing titles yields &lt;code&gt;{interface: {members}}&lt;/code&gt; for the entire surface. Two patterns in the prose supply the rest of what the resolver needs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern in the docs&lt;/th&gt;
&lt;th&gt;What it yields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Interface-member&lt;/code&gt; chunk titles&lt;/td&gt;
&lt;td&gt;every member of every interface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accessed using the global property "layout"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;layout&lt;/code&gt; resolves to &lt;code&gt;Layout&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Parent interface: Block&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;inherited members&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The middle row is what makes receiver typing possible at all. The set of documented globals, the thing that turns &lt;code&gt;layout.x&lt;/code&gt; into a decidable question rather than a guess, is extracted from a sentence the documentation happens to write about itself.&lt;/p&gt;

&lt;p&gt;A second datagraph describes &lt;strong&gt;constructs&lt;/strong&gt; rather than members: how each one is declared, and which calls open and close it. It is generated from the API definitions rather than curated, and the reason is the best argument in the codebase for never hand-writing ground truth:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hand-curating it is error-prone: the first cut invented a &lt;code&gt;List&lt;/code&gt;/&lt;code&gt;listStart&lt;/code&gt; construct that does not exist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We hallucinated an API while building the tool whose entire job is catching hallucinated APIs. The generator now derives the pairs mechanically: on a global-reachable interface, a &lt;code&gt;&amp;lt;base&amp;gt;Start&lt;/code&gt;/&lt;code&gt;&amp;lt;base&amp;gt;End&lt;/code&gt; pair whose Start takes a defined interface as its first argument. Pairs whose first argument is a primitive, like &lt;code&gt;keepStart(string)&lt;/code&gt;, are not constructs anyone instantiates, so they are skipped and reported rather than guessed at. Seven constructs survive that test.&lt;/p&gt;

&lt;p&gt;An entry looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"interface"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"paired"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aliases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"grid"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"declare"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"new Table"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"layout.tableStart"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"layout.tableEnd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"no_close"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Rows and cells end IMPLICITLY. There is NO tableCellEnd and NO tableRowEnd; those methods DO NOT EXIST."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last field is the interesting one, because it is a &lt;strong&gt;negative&lt;/strong&gt; fact. Some constructs close implicitly, so the end-calls a developer would reasonably expect are absent, and &lt;code&gt;tableCellEnd&lt;/code&gt; is precisely what the model kept reaching for. The fact is authored once and read by both the checker and the terminology builder, so the two can never drift into disagreeing about it.&lt;/p&gt;

&lt;p&gt;It also upgrades the finding. Instead of "no such member", which sends the agent hunting for a replacement that does not exist, the checker can say the call is unnecessary and the row closes on its own, so deleting it loses nothing. A checker that only knows what is absent can report; one that knows &lt;em&gt;why&lt;/em&gt; it is absent can tell the agent what to do instead.&lt;/p&gt;

&lt;p&gt;With the index in place, three further properties are what make the check survivable in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Precision over coverage
&lt;/h3&gt;

&lt;p&gt;A member is only judged when the receiver's type is certain: a documented global, a &lt;code&gt;new X()&lt;/code&gt;, or an interface named directly. A parameter, a function's return value, a nested property, all skipped.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a wrong accusation is worse than a miss: it sends the agent off to "fix" working code, and it teaches the user to ignore the check.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Eight check families, each of which exists because something landed
&lt;/h3&gt;

&lt;p&gt;A member check alone is not enough, and every gap here was found in production rather than in design.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What landed&lt;/th&gt;
&lt;th&gt;Why it slipped the previous checks&lt;/th&gt;
&lt;th&gt;Check added&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;engine.loadXML(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;undeclared receiver, so no type to resolve&lt;/td&gt;
&lt;td&gt;object check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;loadXMLData()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a bare call defined nowhere&lt;/td&gt;
&lt;td&gt;function check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;const&lt;/code&gt;, arrow functions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;node --check&lt;/code&gt; passes what the target engine cannot run&lt;/td&gt;
&lt;td&gt;syntax check (ES3)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;layout.blockEnd({ borderColor: 'red' })&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;an invented key inside an object literal, not a dotted member&lt;/td&gt;
&lt;td&gt;option check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;new ActiveXObject("Microsoft.XMLDOM")&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;member check cannot type an unknown constructor&lt;/td&gt;
&lt;td&gt;constructor check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Object.keys&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;valid JS, absent from an ES3-era engine&lt;/td&gt;
&lt;td&gt;engine check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a referenced data file that does not exist&lt;/td&gt;
&lt;td&gt;not a symbol at all&lt;/td&gt;
&lt;td&gt;file check&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;strong&gt;option check&lt;/strong&gt; is the one I would not have thought of. An invented member on a dotted call is obvious. An invented &lt;em&gt;key&lt;/em&gt; smuggled through an options object is invisible to a member check, and at runtime it is silently ignored, so the behaviour you promised the user simply never happens and nothing anywhere reports an error.&lt;/p&gt;

&lt;h3&gt;
  
  
  A checker's false positive is worse than a miss
&lt;/h3&gt;

&lt;p&gt;We learned this twice, expensively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Once by accident.&lt;/strong&gt; A markdown fence in a knowledge-base snippet tripped a template-literal check and a real API was branded invented. The model then wrote that into its session memory: it learned that a real API does not exist, and carried that forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Once systematically.&lt;/strong&gt; The target engine implements a genuine W3C DOM, and our browser-API denylist had been written on the assumption that DOM methods were browser-only. Of the 34 names on it, &lt;strong&gt;23 were real APIs in this engine&lt;/strong&gt;. The check had been flagging the agent's &lt;em&gt;correct&lt;/em&gt; code, and it had helped cause the spiral in section 5.&lt;/p&gt;

&lt;p&gt;Every new check now ships with a known-good-input case for both shapes it audits, whole files and fenced snippets.&lt;/p&gt;

&lt;h3&gt;
  
  
  The runtime layer
&lt;/h3&gt;

&lt;p&gt;Static analysis has a hard limit: it must skip any receiver whose type is uncertain, which is precisely where dynamic code lives. So the last check generates a fake engine from the documentation index and actually runs the build against it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The docs are COMPLETE and enumerable. So a fake engine can be GENERATED from them: every documented global and constructor exists; every documented member is a callable, chainable stub; touching an UNDOCUMENTED member on a typed object records an INVENTED finding. &lt;code&gt;loadJS&lt;/code&gt;/&lt;code&gt;importJS&lt;/code&gt; resolve real project files, so wiring is exercised for real.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It catches syntax errors and crashes, loads of files that do not exist, and invented members reached through dynamic flow: parameters and return values, exactly the receivers the static pass must skip. It runs once at the end of a turn, never per write, because one process launch per draft is cheap and one per write is not.&lt;/p&gt;

&lt;p&gt;The limits are stated in the same docstring, which is the part I would want if I were adopting this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What it can NOT prove: rendering. Data APIs return permissive stubs (no real XML), so loops may run zero iterations and zero-iteration paths go unexercised. This is a dry run, "the code executes against the documented surface", not a render test.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. A finding that does not block is decoration
&lt;/h2&gt;

&lt;p&gt;I built the checker, wired it in, and watched it work. It caught the invented APIs and reported them accurately, and nothing whatsoever happened as a result:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Told "these APIs do not exist, fix them now", the model wrote the user a prose essay ("you may need to rely on...") and finished the turn with the invented symbols still on disk. So findings also gate the END of the turn: a &lt;code&gt;finish_gate&lt;/code&gt; the tool loop consults before accepting a final answer, which pushes the model back to work while anything is outstanding (same lesson as &lt;code&gt;lookup_before_write&lt;/code&gt;: enforce it, don't ask for it).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The gate contract fits in one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returning a string means the draft was &lt;strong&gt;not sent&lt;/strong&gt; and that string goes back into the model's context. Returning &lt;code&gt;None&lt;/code&gt; ships it.&lt;/p&gt;

&lt;p&gt;Everything else is about the budget, and the budget is where the design decisions are.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-gate bounds compound
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gate&lt;/th&gt;
&lt;th&gt;Own push budget&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;verify&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bail&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claim&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;checklist&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each is individually reasonable, and together they can bounce one turn six times, which is an unpredictable worst case for latency and cost. So there is a &lt;strong&gt;shared turn budget of 3&lt;/strong&gt; across every gate, and past it the composed gate goes silent and the turn ends with whatever honesty the pushes bought.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which produced the next bug
&lt;/h3&gt;

&lt;p&gt;Twice, every push was consumed by intermediate holds and a false final claim shipped unchallenged. Hence a reserve:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;reserve_final&lt;/code&gt; keeps the last (user-visible) draft policed after the shared budget is spent mid-turn. Past &lt;code&gt;max_pushes&lt;/code&gt;, up to &lt;code&gt;reserve_final&lt;/code&gt; further pushes may be spent, but ONLY on NEW material: a draft whose text materially changed since the last hold, or new deeds. A verbatim doubled-down draft has already ignored this correction once, repeating it buys nothing, so it ends the turn as before.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Refuse before the write, not after
&lt;/h3&gt;

&lt;p&gt;This was the single biggest behavioural improvement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Auditing after the fact meant asking the model that just invented an API to un-invent it. It would rather explain, annotate, or stand by it. Refused content simply never becomes a file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Police claims, not just code
&lt;/h3&gt;

&lt;p&gt;The gate that catches the most is the simplest. Asked to split a file, the model created three modules and reported four:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Changes made:
  1. Created xmlLoader.js ...
  2. Created styledBox.js ...
  3. Created tableCreator.js ...
  4. Updated template.js to import and use the new files.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4 never happened. It had planned it and narrated the plan in the past tense. The check is mechanical: past-tense verbs only, scoped to a single line, matched against &lt;code&gt;ctx.files_changed&lt;/code&gt;, which every landed write funnels through.&lt;/p&gt;

&lt;p&gt;The tense restriction is not incidental, and the comment explaining why earns its length:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# PAST TENSE ONLY, and deliberately so.
#   - "I will update template.js" is a plan, not a deed - the future tense must not trip this.
#   - The present tense describes what CODE does, not what the agent did: "template.js already
#     imports xmlLoader.js" and "createTable() creates a new Table" are statements of fact
#     about the project, and reading them as claims would gate honest, read-only answers.
# Only the past tense asserts "I did this", which is the thing the file system can contradict.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scope restriction matters just as much: matching is per line, because a wider window lets one "Created" three bullets up capture every filename mentioned afterwards.&lt;/p&gt;

&lt;p&gt;And the gate declares its own non-goal, which I think is why it works:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This checks only what the model VOLUNTEERS. It does not police silence... A false claim is wrong unconditionally; that is why this gate can be mechanical.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two smaller rules that took real failures to learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strike caps&lt;/strong&gt; exist because the model can be wrong about how to fix something and must not be trapped re-trying forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Findings are split into introduced and inherited&lt;/strong&gt;, after a user complained: &lt;em&gt;"I only asked it to do 1 simple thing and it did it, but it also started talking about jsonData."&lt;/em&gt; Only a finding that a write in this turn introduced may hold the turn.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. How the system talks to itself
&lt;/h2&gt;

&lt;p&gt;Every gate above works by writing a message into the model's own context. Getting the &lt;em&gt;wording&lt;/em&gt; of those messages right turned out to be as load-bearing as the logic that triggers them, and it is the part I have seen discussed least.&lt;/p&gt;

&lt;p&gt;Start with the two constants that open and close every one of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The envelope
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#: Opens every gate message. Injected corrections must not read as the user speaking, or the
#: model treats its own gated draft as user feedback.
&lt;/span&gt;&lt;span class="n"&gt;ENVELOPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[automated pre-send check - NOT a message from the user] &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not a stylistic choice. Without the marker the model reads its own blocked draft as something the user saw and objected to, and then reasons about it as if a conversation had happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  The silence trailer
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#: Closes every gate message. Without it the model apologises to the user for a draft they never
#: saw ("You are correct. I apologise..."), explains itself, and asks permission, turning a silent
#: retry into a wall of confusing chat. The gate is plumbing and must stay invisible.
&lt;/span&gt;&lt;span class="n"&gt;SILENCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do not apologise. Do not mention this check, your draft, or any mistake - the user &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
           &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;never saw them and will not understand a reference to them. Do not ask permission: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
           &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;just do the work. Your reply must read as if you had done it right the first time. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
           &lt;span class="c1"&gt;# Live: a bail push made the model REWRITE a file smaller, deleting footer code
&lt;/span&gt;           &lt;span class="c1"&gt;# that had already passed the symbol check, and surrender in a comment instead.
&lt;/span&gt;           &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Never respond to this check by deleting or stripping code that already passed &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
           &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;its checks - fix FORWARD (add, complete, correct), never retreat.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every sentence in that string is a bug, and the last one is the strangest: a push meant to stop the agent giving up made it delete working, already-verified code and write an apology in a comment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Correction pressure has a direction, and if you do not specify it, the model will sometimes satisfy you by removing the problem rather than solving it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both constants live in one module because they did not, once, and a hand-copied version quietly drifted out of sync.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three of the messages, chosen for what each one teaches
&lt;/h3&gt;

&lt;p&gt;&lt;b&gt;Escalation on repetition&lt;/b&gt; - the second push stops being polite&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SECOND refusal: you have made ZERO writes this turn, the file system is the ground truth
and it shows no change, yet your draft still reports work as done. It is false.

Do ONE of these, nothing else: (1) make the change now with a write tool and then
describe it, or (2) answer exactly what is true: that you changed nothing, and what you
found in the file instead.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The first time the model claims work it did not do, it gets a straightforward correction. This is what it gets if it doubles down.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Constraining what the model then tells the user&lt;/b&gt; - the budget message&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[lookup budget exhausted - {live} knowledge lookups have run this turn] No further
lookups will run. Searching again, in any wording, returns this same message. STOP
searching and WRITE... In that final answer, if information is missing, say the SEARCH
BUDGET ran out, do NOT claim the knowledge base lacks it; the budget ending says nothing
about what the documentation contains.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The obvious message is "stop searching". The last clause took a live failure to learn: without it, our own rate limit came out of the agent's mouth as a factual claim about the customer's documentation. The agent had no way to know the difference, so we had to tell it.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;A checker that argues&lt;/b&gt; - pre-empting the three moves the model actually makes&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is not "unverified", the documentation is COMPLETE, and these were checked against
every member of the interface. You invented them just now, most likely because a similar
name exists (tableStart exists, so tableCellEnd feels like it must). It does not. The
file will fail on the real engine.

Rewrite those calls using the real members listed above. Do NOT re-run the same
rag_lookup that produced this code; it will return the same wrong example. If you need a
pattern, rag_lookup a DIFFERENT, more specific goal. Do NOT annotate the call and move
on; do NOT keep it because it looks right. If the interface genuinely has no member for
what you intended, leave that part out and say so in your final answer, never leave a
call to a method that does not exist.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Note that it names the failed retrieval explicitly. Without that line the agent re-runs the lookup that produced the bad code in the first place, which is the closed loop from section 5.&lt;/p&gt;

&lt;h3&gt;
  
  
  The trust boundary between agents
&lt;/h3&gt;

&lt;p&gt;When a plan is decomposed, each item runs as a bounded worker with its own context, and the worker's report comes back to the composer as a user message. Workers are told to end with what they built and what it exposes.&lt;/p&gt;

&lt;p&gt;The composer does not believe the "exposes" line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exposures&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;One line describing what a landed file exposes - MECHANICAL over the file itself
    (the file is a fact; a model-authored &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exposes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; line is a claim). Falls back to the
    worker&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s own short answer for non-JS / no matches.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That parenthesis is the whole article in one sentence. Everywhere the system has a choice between what a model says happened and what the file system says happened, it takes the file system, and where it cannot, it says so and degrades deliberately.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Some mistakes should not be catchable
&lt;/h2&gt;

&lt;p&gt;The fix for this last one runs against every instinct: it is to take intelligence away, not add more.&lt;/p&gt;

&lt;p&gt;Asked to remove a table, the agent removed one line, rewrote everything around it, and made the file &lt;strong&gt;bigger&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attempt&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;original&lt;/td&gt;
&lt;td&gt;1527 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"remove the table"&lt;/td&gt;
&lt;td&gt;1653 bytes, reported as success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;retry&lt;/td&gt;
&lt;td&gt;1659 bytes, reported as success&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The diagnosis had two layers, and both are worth knowing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The removal instruction fell into a generative chunk-rewrite path, so "remove" became "rewrite this region, minus the thing".&lt;/li&gt;
&lt;li&gt;The forced pre-write lookup for &lt;em&gt;"how to remove a table"&lt;/em&gt; returned the documentation for &lt;strong&gt;creating&lt;/strong&gt; one, because that is what the corpus has. That poisoned the intent: the agent came away believing you remove a table by calling &lt;code&gt;tableEnd()&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix was to take the generation out of that step entirely. The model reads the file and emits &lt;code&gt;L&amp;lt;n&amp;gt;: DELETE&lt;/code&gt; markers, nothing else, and ordinary code drops those lines:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Delete-only pass: the model reads the file (or a &lt;code&gt;where=&lt;/code&gt;-scoped region) and marks which lines belong to the thing being removed; they are dropped deterministically. It can neither rewrite nor add, so a removal can only shrink the file. This is where EVERY pure deletion goes instead of the generative chunk rewrite (which deleted one line and grew the file live).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The same file went from 1527 bytes to &lt;strong&gt;678&lt;/strong&gt;, with the right construct gone and the one next to it untouched.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it all amounts to
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Catches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;filename matching, construct footprints&lt;/td&gt;
&lt;td&gt;literal search missing the obvious&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;identical-call breaker, turn budget, fuzzy cache, in-flight collapse&lt;/td&gt;
&lt;td&gt;fixation and rewording loops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;query inflation with an adaptive brake&lt;/td&gt;
&lt;td&gt;one phrasing being a single point of failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;resolve_topic&lt;/code&gt;, confidence ladder, de-nouning rewrite&lt;/td&gt;
&lt;td&gt;goal words that miss an API-indexed corpus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;self-auditing lookup shadow, uncacheable flagged answers&lt;/td&gt;
&lt;td&gt;the retrieval tool inventing APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;check_symbols&lt;/code&gt; (8 families) + the &lt;code&gt;dry_run&lt;/code&gt; shim&lt;/td&gt;
&lt;td&gt;invented APIs, statically and at runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;composed finish gates, pre-write refusal, claim audit&lt;/td&gt;
&lt;td&gt;findings being ignored, work claimed but not done&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;envelope, silence trailer, escalating pushes&lt;/td&gt;
&lt;td&gt;the correction itself being misread or over-obeyed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;delete-scan&lt;/td&gt;
&lt;td&gt;a destructive edit that could grow the file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Across all of this, the model never got better. Every version ran on the same tier that failed the first time.&lt;/p&gt;

&lt;p&gt;What changed was the space of things it could get away with: what it could search without a limit, what it could claim without evidence, what it could write without being refused, and what it could remove by generating rather than by marking.&lt;/p&gt;

&lt;p&gt;That is unglamorous work, and it is almost all of the work.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>rag</category>
      <category>agents</category>
    </item>
    <item>
      <title>Creating a web API with Lua using Nginx OpenResty</title>
      <dc:creator>Joe Buckle</dc:creator>
      <pubDate>Sun, 02 Feb 2020 16:58:55 +0000</pubDate>
      <link>https://dev.to/joebuckle-dev/creating-an-api-with-lua-using-openresty-42mc</link>
      <guid>https://dev.to/joebuckle-dev/creating-an-api-with-lua-using-openresty-42mc</guid>
      <description>&lt;p&gt;I'm a bit of an old fashioned, pragmatic developer and tend to stick with frameworks and patterns that I know are reliable - a 'stick in the mud' if you will.  &lt;/p&gt;

&lt;p&gt;This dive is deviating from my usual path.&lt;/p&gt;

&lt;p&gt;I had never considered Lua to be a viable language to create a backend for a web application. Of course I'm not totally oblivious to the language itself - I do know it's used mostly for embedding in other software and game engines - but that's it really. &lt;/p&gt;

&lt;p&gt;I came across the &lt;a href="https://openresty.org" rel="noopener noreferrer"&gt;OpenResty&lt;/a&gt; project whilst on a learning journey of &lt;a href="https://www.nginx.com/" rel="noopener noreferrer"&gt;Nginx&lt;/a&gt; web server.&lt;/p&gt;

&lt;p&gt;Writing code directly on the web server rather than passing the request to some interpreter is a very interesting concept for me. &lt;br&gt;
Lua runs directly inside the Nginx worker which means a very small barrier between the webserver and the application code. &lt;br&gt;
Additionally, Lua is known for being immensely fast compared to interpreted languages particularly when using the LuaJIT compiler. &lt;/p&gt;

&lt;p&gt;Nginx is capable of handling a very high number of concurrent connections at a very low memory footprint.   &lt;/p&gt;

&lt;p&gt;However, there doesn't appear to be that much excitement about it. &lt;br&gt;
It's been around for some time (at least since 2016) but it does appear to be lacking in developer adoption. Saying that, some big names are using it, including Cloudflare and Tumbler.&lt;/p&gt;

&lt;p&gt;Despite that - the performance gain from this sort of backend has peaked my interest.  &lt;/p&gt;

&lt;p&gt;The easiest way (perhaps the only way) to get started with this is by installing &lt;a href="https://openresty.org/en/" rel="noopener noreferrer"&gt;OpenResty&lt;/a&gt;. &lt;br&gt;
This provides the Nginx web server with the Lua module.&lt;/p&gt;

&lt;p&gt;If you're familiar with Nginx this example will make sense to you. The location directive is being passed a Lua script as a file. &lt;br&gt;
The output from that script is expected to be in JSON in this configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location ~ ^/api(.*)$ {
  default_type 'text/json';
  add_header 'Content-Type' 'application/json';
  content_by_lua_file /etc/openresty/sites/api.lua; # &amp;lt; Points to Lua file
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also pass arbitrary code into &lt;code&gt;content_by_lua&lt;/code&gt; and &lt;code&gt;content_by_lua_block&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Now, Lua is a reasonable simple language to pick up if you're used to Ruby or Python. I hadn't used it before but I was still able to quickly write a script that allows creation of API endpoints and parses the body and parameters. &lt;/p&gt;

&lt;p&gt;There is an object you can access in Lua called &lt;code&gt;ngx&lt;/code&gt;. This provides you with the data passed into Nginx from the request such as the body or the path etc...  &lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_method&lt;/span&gt; &lt;span class="c1"&gt;-- POST, GET.. whatever&lt;/span&gt;
&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_body_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;-- The data passed in from the request&lt;/span&gt;
&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="c1"&gt;-- The request path&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script I wrote allows me to set the Method and Endpoints that are allowed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="cm"&gt;--[[ api.lua --]]&lt;/span&gt;

&lt;span class="c1"&gt;-- Helper functions&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;strSplit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delim&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;substr&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;string.gmatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"[^"&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;delim&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="s2"&gt;"]*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;substr&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nb"&gt;string.len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
            &lt;span class="nb"&gt;table.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- Read body being passed&lt;/span&gt;
&lt;span class="c1"&gt;-- Required for ngx.req.get_body_data()&lt;/span&gt;
&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_body&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;-- Parser for sending JSON back to the client&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;cjson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"cjson"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;-- Strip the api/ bit from the request path&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;reqPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;"api/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- Get the request method (POST, GET etc..)&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;reqMethod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_method&lt;/span&gt;
&lt;span class="c1"&gt;-- Parse the body data as JSON&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_body_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt;
        &lt;span class="c1"&gt;-- This is like a ternary statement for Lua&lt;/span&gt;
        &lt;span class="c1"&gt;-- It is saying if doesn't exist at least&lt;/span&gt;
        &lt;span class="c1"&gt;-- define as empty object&lt;/span&gt;
        &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;cjson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_body_data&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="n"&gt;Api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Api&lt;/span&gt;
&lt;span class="c1"&gt;-- Declare API not yet responded&lt;/span&gt;
&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Function for checking input from client&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;-- If API not already responded&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responded&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="c1"&gt;-- KeyData = params passed in path&lt;/span&gt;
        &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;keyData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="c1"&gt;-- If this endpoint has params&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;string.find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;(.-)&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;then&lt;/span&gt;
            &lt;span class="c1"&gt;-- Split origin and passed path sections&lt;/span&gt;
            &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;splitPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strSplit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;splitReqPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strSplit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reqPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;-- Iterate over splitPath&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;splitPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
                &lt;span class="c1"&gt;-- If chunk contains &amp;lt;something&amp;gt;&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;string.find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;(.-)&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;then&lt;/span&gt;
                    &lt;span class="c1"&gt;-- Add to keyData&lt;/span&gt;
                    &lt;span class="n"&gt;keyData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;string.match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"%&amp;lt;(%a+)%&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;splitReqPath&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                    &lt;span class="c1"&gt;-- Replace matches with default for validation&lt;/span&gt;
                    &lt;span class="n"&gt;reqPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;string.gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reqPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;splitReqPath&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;end&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;-- return false if path doesn't match anything&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reqPath&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
        &lt;span class="k"&gt;then&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="c1"&gt;-- return error if method not allowed&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reqMethod&lt;/span&gt; &lt;span class="o"&gt;~=&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;
        &lt;span class="k"&gt;then&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;cjson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="nb"&gt;error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Method "&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;reqMethod&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="s2"&gt;" not allowed"&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;-- Make sure we don't run this again&lt;/span&gt;
        &lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;-- return body if all OK&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keyData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keyData&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I can call &lt;code&gt;Api.endpoint()&lt;/code&gt; following to create my endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="cm"&gt;--[[ api.lua --]]&lt;/span&gt;
&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;cjson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'/test/&amp;lt;id&amp;gt;/&amp;lt;name&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ngx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;say&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;cjson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So this has got me started. Obviously the logic inside these endpoints would be much more complex and usually have to connect to databases - all of which &lt;a href="https://github.com/bungle/awesome-resty" rel="noopener noreferrer"&gt;OpenResty supports&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;One thing I did come across was the &lt;a href="https://leafo.net/lapis/" rel="noopener noreferrer"&gt;Lapis Framework&lt;/a&gt; created by &lt;a href="https://leafo.net/" rel="noopener noreferrer"&gt;Leafo&lt;/a&gt; who I know mostly from creating the PHP compilers for &lt;a href="https://leafo.net/lessphp/" rel="noopener noreferrer"&gt;Less&lt;/a&gt; and &lt;a href="https://leafo.net/scssphp/" rel="noopener noreferrer"&gt;SCSS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Github for code the code here - &lt;a href="https://github.com/bambattajb/openresty-api-example" rel="noopener noreferrer"&gt;https://github.com/bambattajb/openresty-api-example&lt;/a&gt;,&lt;/p&gt;

</description>
      <category>lua</category>
      <category>nginx</category>
      <category>openresty</category>
      <category>lapis</category>
    </item>
    <item>
      <title>Embedded UI Components with Preact</title>
      <dc:creator>Joe Buckle</dc:creator>
      <pubDate>Tue, 24 Dec 2019 10:34:14 +0000</pubDate>
      <link>https://dev.to/joebuckle-dev/embedded-ui-components-with-preact-2ik6</link>
      <guid>https://dev.to/joebuckle-dev/embedded-ui-components-with-preact-2ik6</guid>
      <description>&lt;p&gt;As a front-end developer it's quite common to be tasked with creating a new UI component for an application. &lt;/p&gt;

&lt;p&gt;You're given access to some backend API and you have to crack on and build a UI that sits somewhere inside the website as a widget, page or whatever.&lt;/p&gt;

&lt;p&gt;The component is complex enough that you need to manage view states, so you'd quite like a system for that. &lt;br&gt;
You also like using the cleaner ES6 syntax and you're already familiar with React and JSX and all that jazz.&lt;/p&gt;

&lt;p&gt;Now, you don't have access to the applications overarching build tools - you just need to find a way of passing some reasonably sized and efficiently written code over to the application maintainers who will factor it into their monolith 😊. &lt;/p&gt;

&lt;p&gt;I tend to see these UI components as their own small compartmentalised applications but I am fully conscious of the increased size when shipping seemingly unnecessary frameworks in packaged code. &lt;/p&gt;

&lt;p&gt;So React is out of the question because it's &lt;strong&gt;&amp;gt; 30kb&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I eventually discovered &lt;a href="https://preactjs.com/" rel="noopener noreferrer"&gt;Preact&lt;/a&gt; which claimed to come in at &lt;strong&gt;3kb&lt;/strong&gt; and supports all of the Virtual DOM and State Management features of React. &lt;/p&gt;

&lt;p&gt;I totally signed up and was not disappointed. Just set up a Webpack build environment and away you go. &lt;/p&gt;

&lt;h2&gt;
  
  
  How is Preact so small?
&lt;/h2&gt;

&lt;p&gt;React has its own &lt;a href="https://reactjs.org/docs/events.html" rel="noopener noreferrer"&gt;Synthetic Event System&lt;/a&gt; for handling events and Preact uses the browsers native &lt;code&gt;addEventListener&lt;/code&gt;. &lt;br&gt;
This could lead to a user experience penalty. &lt;/p&gt;

&lt;p&gt;React is trying very hard (30kb + hard) to ensure a consistent feel in whatever browser it's being run in; whereas Preact has chosen to use unpredictable browser-native event listeners for a massively reduced packaged size.&lt;/p&gt;

&lt;p&gt;More on the 'subtle' differences here - &lt;a href="https://preactjs.com/guide/v10/differences-to-react#main-differences" rel="noopener noreferrer"&gt;Main Differences&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's missing?
&lt;/h2&gt;

&lt;p&gt;Well, &lt;a href="https://preactjs.com/guide/v8/differences-to-react/#whats-missing" rel="noopener noreferrer"&gt;not much&lt;/a&gt;! &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Proptypes&lt;/code&gt; (they aren't used all the time anyway)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Children&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Preact is a great solution to my problem but it does look as though it might come with some UI performance trade-offs leading to an inconsistent UX. &lt;/p&gt;

&lt;p&gt;That said, if you chose to build and embedded UI component without a framework you most likely wouldn't create your own synthetic event system for seamlessly handling differences in native events.&lt;/p&gt;

&lt;p&gt;Preact is totally winning for me in this context 👍&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I love WordPress, but I don't like its ecosystem</title>
      <dc:creator>Joe Buckle</dc:creator>
      <pubDate>Sat, 21 Dec 2019 15:24:21 +0000</pubDate>
      <link>https://dev.to/joebuckle-dev/i-love-wordpress-but-i-don-t-like-its-ecosystem-36f8</link>
      <guid>https://dev.to/joebuckle-dev/i-love-wordpress-but-i-don-t-like-its-ecosystem-36f8</guid>
      <description>&lt;p&gt;Come gather round friends and I'll tell you a tale&lt;br&gt;
Of when I claimed to be a WordPress guru&lt;br&gt;
But was then made to use off-the-shelf themes and plugins&lt;br&gt;
And that claim I sought to undo&lt;/p&gt;

&lt;p&gt;I've been using WordPress to solve content management problems for clients in the digital publishing industry for around 10 years and know its core features pretty well having spent countless hours traversing through the codex and reading a plethora of problem-solving developer articles.    &lt;/p&gt;

&lt;p&gt;I am an advocate for the software and push it as a low-cost, easy to use, extensible and familiar content editing &amp;amp; publishing platform for digital publishing teams.&lt;/p&gt;

&lt;p&gt;From a developer perspective the core provides a very good and efficient Query Engine, Database Abstraction Layer, Rest API Framework, User / Roles management and loads more.&lt;/p&gt;

&lt;p&gt;It also provides a decent admin interface that is intuitive and adaptable enough that I can tailor it to specific business requirements. &lt;/p&gt;

&lt;h2&gt;
  
  
  What's not to like?
&lt;/h2&gt;

&lt;p&gt;Well, last year I assumed the role of "WordPress Guru". &lt;/p&gt;

&lt;p&gt;Up until then I had been using WordPress as a toolkit for building a unique set of features for my clients. I would be writing a lot of specific business logic around this toolkit just as you would a framework.&lt;/p&gt;

&lt;p&gt;At the time of accepting this role I didn't know which sites I'd be working on and just assumed that, armed with this knowledge, I should be able to handle it right?&lt;/p&gt;

&lt;h2&gt;
  
  
  But I was wrong!
&lt;/h2&gt;

&lt;p&gt;You see, there is, in my opinion, a "Dark Side" to WordPress - its Theme and Plugin culture.&lt;/p&gt;

&lt;p&gt;It's not like I was unfamiliar with this and whenever I'd read up or hear of this culture I thought it was individuals or small businesses on small budgets looking to get a website up as cheap as possible and that it shouldn't apply to serious businesses or organisations. &lt;/p&gt;

&lt;h2&gt;
  
  
  I was wrong again!
&lt;/h2&gt;

&lt;p&gt;I used the term "Dark Side" because I would be dealing with content managers that have a business requirement for something that is more flexible than the feature-restrictive theme or plugin. At some point this system was sold to them either by someone in-house or a developer that is now long gone. &lt;/p&gt;

&lt;p&gt;The first problem is you have a client that doesn't understand "why" this feature is difficult to implement (they're right - it shouldn't be, if this system was built properly). &lt;br&gt;
The second is the developer trying to shoehorn features into this system knowing it's not really fit for purpose thus making their ongoing maintenance work more difficult.&lt;/p&gt;

&lt;p&gt;Sometimes you have to do things like "modify core code" of plugins only to learn of a "major security update" some weeks later that you now have to factor in.&lt;/p&gt;

&lt;p&gt;Sites end up becoming a maintenance and security headache.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson learned
&lt;/h2&gt;

&lt;p&gt;I will never claim to be a "WordPress Guru" again as putting me in an environment where I have to build or extend sites that use WooThemes or Divi (for example) then I am lost!&lt;/p&gt;

&lt;p&gt;Someone who is a "WordPress Guru" would understand the entire ecosystem of available WordPress products - not just its core system.  &lt;/p&gt;

&lt;p&gt;My advice for publishers is if you are looking at purchasing a Theme or Plugin to handle a feature you're needing make sure it does EVERYTHING you're ever going to need. &lt;/p&gt;

&lt;p&gt;Otherwise consult a developer - there are some good quality plugins that allow themselves to be extended by using custom hooks and filters, perhaps enough so to fill the void in requirements. &lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
    </item>
  </channel>
</rss>
