<?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</title>
    <description>The most recent home feed on DEV Community.</description>
    <link>https://dev.to</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/rss/"/>
    <language>en</language>
    <item>
      <title>The LLM Arbiter's 'Common Sense' Correction: The Fatal Flaw of Shorting AI Tokens in a Risk-On Market</title>
      <dc:creator>Kestrel Quant</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:20:54 +0000</pubDate>
      <link>https://dev.to/kestrelquant/the-llm-arbiters-common-sense-correction-the-fatal-flaw-of-shorting-ai-tokens-in-a-risk-on-4hkp</link>
      <guid>https://dev.to/kestrelquant/the-llm-arbiters-common-sense-correction-the-fatal-flaw-of-shorting-ai-tokens-in-a-risk-on-4hkp</guid>
      <description>&lt;h1&gt;
  
  
  The LLM Arbiter's 'Common Sense' Correction: The Fatal Flaw of Shorting AI Tokens in a Risk-On Market
&lt;/h1&gt;

&lt;p&gt;It’s 1:05 AM. The crypto markets are quietly grinding upward, and your quantitative trading bot is fully automated, scanning hundreds of pairs across multiple timeframes. Suddenly, the base scoring engine fires a technically pristine SHORT signal for &lt;code&gt;TAOUSDT&lt;/code&gt;. The moving averages have crossed, the RSI is overbought on the 15-minute chart, and the order execution module spools up to send the market sell order to the exchange. &lt;/p&gt;

&lt;p&gt;But before the order hits the wire, a secondary system intercepts the request. It reviews the broader market context, identifies a fatal narrative flaw in the trade, and slams the kill switch. &lt;/p&gt;

&lt;p&gt;The trade is vetoed. You just avoided a brutal short squeeze. &lt;/p&gt;

&lt;p&gt;This is the power of an LLM-based trading Arbiter. In this deep dive, we’ll break down how context-aware execution layers use macro context and narrative awareness to veto quantitatively triggered trades, using a real-world &lt;code&gt;TAOUSDT&lt;/code&gt; log analysis to show developers how to build this "common sense" layer into their own systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Quantitative Blindspot
&lt;/h2&gt;

&lt;p&gt;Pure technical analysis and raw quantitative models are inherently backward-looking. They excel at identifying statistical anomalies in price action, volume, and order book imbalances. However, they suffer from a massive blindspot: they are entirely devoid of macro awareness and narrative context.&lt;/p&gt;

&lt;p&gt;In traditional finance, a stock might drop on bad technicals despite a bullish sector. In crypto, narrative is king. A technically "overbought" token in a raging sector narrative isn't due for a reversal; it's due for a parabolic expansion. When a base quant model ignores market regime shifts—like transitioning from a risk-off chop to a aggressive risk-on rally—it will inevitably try to short the strongest assets in the market. &lt;/p&gt;

&lt;p&gt;Raw math doesn't know that TAO (Bittensor) is the current darling of the AI narrative. It just sees a red candle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: A Technically Sound, Contextually Fatal Signal
&lt;/h2&gt;

&lt;p&gt;Let’s look at the exact moment the base model generated the signal. The system was running its routine sweeps. The scoring engine evaluated &lt;code&gt;TAOUSDT&lt;/code&gt; and, based purely on its internal technical thresholds, prepared a SHORT execution. &lt;/p&gt;

&lt;p&gt;Here is a sanitized snippet of the system logs from that exact moment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-10-20 01:05:28,331 [INFO] scoring_engine: F-229/F-230: Elastic threshold: 80 → 70 
2026-10-20 01:05:31,029 [WARNING] scoring_engine: Technical confirmation failed for 1000PEPEUSDT 15m...
...
[LLM ARBITER INTERCEPT INITIATED]
Target: TAOUSDT
Direction: SHORT
Base System Score: 47.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The base model saw a score of 47.3. While technically below the standard swing threshold, the elastic threshold logic in the engine was considering it for a sub-position entry. The technicals screamed "mean reversion." The bot was ready to short a leading AI token.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Intervention: Enter the LLM Arbiter
&lt;/h2&gt;

&lt;p&gt;This is where the LLM Arbiter takes over. Instead of just looking at the price chart, the Arbiter intercepts the execution request and initiates a contextual review. It pulls real-time macro data, on-chain positioning, and sector sentiment, injecting them into the LLM's context window.&lt;/p&gt;

&lt;p&gt;The Arbiter fed the LLM the following context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Sentiment:&lt;/strong&gt; Fear &amp;amp; Greed Index (FNG) = 59 (Neutral/Greed)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Positioning:&lt;/strong&gt; Smart Money Long/Short Ratio (LS) = 1.70 (Heavily Long)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Flow:&lt;/strong&gt; Active Buy Ratio (R) = 1.45, continuous stablecoin inflows.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Macro:&lt;/strong&gt; BTC holding near $82,000, Nasdaq rebounding (Classic Risk-On).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Narrative:&lt;/strong&gt; TAO is a core AI-narrative token.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Analysis: Synthesizing the "Common Sense" Veto
&lt;/h2&gt;

&lt;p&gt;The LLM processes this data and generates a &lt;code&gt;_reasoning&lt;/code&gt; log, utilizing Chain-of-Thought to weigh the technical signal against the macro reality. Here is the actual reasoning output from the Arbiter:&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;"final_ruling"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VETO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Let me analyze this final ruling for TAOUSDT SHORT. System score is 47.3, which is weak. But let's consider the context: TAO is an AI-narrative token. Market context: FNG=59 (Neutral/Greed-ish), smart money long (LS=1.70), active buying (R=1.45). Macro news indicates BTC holding near $82k with Nasdaq rebounding — this is a generally bullish, risk-on environment. Shorting into a risk-on environment with smart money long and active buying is counter-trend. Both LS=1.70 and R=1.45 suggest heavy buying pressure. With AI sector strength, shorting TAO seems highly risky and is a recipe for a short squeeze."&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;The LLM identified the fatal flaw: &lt;strong&gt;Shorting a leading AI narrative token while smart money is heavily long in a risk-on environment is financial suicide.&lt;/strong&gt; The technical "overbought" signal was actually just strong momentum. The Arbiter issued a hard &lt;code&gt;VETO&lt;/code&gt;, preventing the trade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer Implementation: Building the Context-Aware Execution Layer
&lt;/h2&gt;

&lt;p&gt;How do you integrate an LLM veto layer into your trading bot without introducing critical latency or hallucination risks? Here are the practical patterns we use.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Asynchronous Context Fetching (Zero Latency Impact)
&lt;/h3&gt;

&lt;p&gt;Never let the LLM fetch data. LLMs are slow; API calls are fast. Your Python/Node backend should fetch the FNG, LS ratios, and macro news via standard REST/WebSocket APIs &lt;em&gt;before&lt;/em&gt; invoking the LLM. Inject this pre-fetched data into the system prompt. The LLM's only job is to &lt;em&gt;evaluate&lt;/em&gt;, not to &lt;em&gt;retrieve&lt;/em&gt;. This keeps the LLM inference time strictly bounded.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Strict JSON Schema &amp;amp; Enum Enforcement
&lt;/h3&gt;

&lt;p&gt;To prevent hallucinations and parsing errors, force the LLM to output a strict JSON schema. Do not ask it for a "paragraph of advice." Ask for a structured ruling.&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;response_schema&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;type&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;json_schema&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;json_schema&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;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;arbiter_ruling&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;strict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;schema&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;type&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;object&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;properties&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;final_ruling&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;type&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;string&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;enum&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;EXECUTE&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;VETO&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;REDUCE_SIZE&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;_reasoning&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;type&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;string&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;confidence_score&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;type&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;number&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;required&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;final_ruling&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;_reasoning&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;additionalProperties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By enforcing the &lt;code&gt;enum&lt;/code&gt; for &lt;code&gt;final_ruling&lt;/code&gt;, your execution engine can simply parse the JSON and execute a &lt;code&gt;switch&lt;/code&gt; statement. No natural language parsing required.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The "Grounding" Prompt Technique
&lt;/h3&gt;

&lt;p&gt;To stop the LLM from hallucinating market conditions, explicitly instruct it to &lt;em&gt;only&lt;/em&gt; use the provided context. &lt;br&gt;
&lt;em&gt;Prompt snippet:&lt;/em&gt; "You are a risk-management Arbiter. You will be provided with a base technical signal and real-time macro context. You must base your reasoning ONLY on the provided data. If the macro context contradicts the technical signal, issue a VETO."&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Fallback Mechanisms
&lt;/h3&gt;

&lt;p&gt;What if the LLM API times out? Your system must have a deterministic fallback. If the Arbiter doesn't return a ruling within 800ms, the system should default to the base quant model's signal, or safely abort the trade. Never block the main execution thread waiting for an LLM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;The integration of an LLM Arbiter represents a paradigm shift in algorithmic trading. We are moving from purely deterministic, math-based execution to &lt;strong&gt;hybrid neuro-symbolic systems&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;The quantitative model is the engine—it generates the raw power and identifies the statistical edges. But the LLM Arbiter is the steering wheel and the brakes. It provides the "common sense" that prevents the engine from driving off a cliff. In crypto, where narratives drive price action far more than traditional fundamentals, ignoring the macro context is a luxury quantitative traders can no longer afford.&lt;/p&gt;

&lt;p&gt;By building context-aware execution layers, developers can create bots that don't just react to price, but actually &lt;em&gt;understand&lt;/em&gt; the market they are trading in.&lt;/p&gt;




&lt;p&gt;Explore advanced AI-driven crypto trading architectures, backtesting tools, and hybrid quant-LLM frameworks at &lt;a href="https://kestrelquant.com" rel="noopener noreferrer"&gt;https://kestrelquant.com&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;⚠️ &lt;strong&gt;Risk Disclaimer&lt;/strong&gt;: Algorithmic and AI-driven trading carry significant risk. LLM reasoning is probabilistic and subject to hallucinations or context window limitations. This article is for educational and technical discussion purposes only and does not constitute financial advice. Never trade with capital you cannot afford to lose.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#algotrading&lt;/code&gt; &lt;code&gt;#crypto&lt;/code&gt; &lt;code&gt;#ai&lt;/code&gt; &lt;code&gt;#buildinpublic&lt;/code&gt;&lt;/p&gt;

</description>
      <category>algotrading</category>
      <category>crypto</category>
      <category>ai</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>From LLM Prototype to Production: The AI Infrastructure Every Engineer Needs to Understand</title>
      <dc:creator>Rashmi Roy</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:20:49 +0000</pubDate>
      <link>https://dev.to/rashmi_roy_447a69fec6d340/from-llm-prototype-to-production-the-ai-infrastructure-every-engineer-needs-to-understand-8g9</link>
      <guid>https://dev.to/rashmi_roy_447a69fec6d340/from-llm-prototype-to-production-the-ai-infrastructure-every-engineer-needs-to-understand-8g9</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fadzsoo535gl230odlof2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fadzsoo535gl230odlof2.jpg" alt="From LLM Prototype to Production: AI infrastructure architecture showing API gateways, model gateways, RAG, caching, observability, security, and production infrastructure" width="596" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your LLM prototype works perfectly.&lt;/p&gt;

&lt;p&gt;You send a prompt, receive a response, and the demo looks impressive.&lt;/p&gt;

&lt;p&gt;Then real users arrive.&lt;/p&gt;

&lt;p&gt;Requests become slower. API costs increase. The model provider experiences an outage. Responses occasionally contain incorrect information. Your logs tell you that something failed, but not why.&lt;/p&gt;

&lt;p&gt;Suddenly, the problem is no longer just about prompting an LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You are now solving an infrastructure and software architecture problem.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Building an AI-powered application is relatively straightforward compared with operating one reliably at scale. The real engineering challenge is designing the system around the model.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore the infrastructure layers engineers should understand before taking an LLM application to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. From Prototype Architecture to Production Architecture
&lt;/h2&gt;

&lt;p&gt;A basic LLM application often looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  |
  v
Backend API
  |
  v
LLM Provider
  |
  v
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is perfectly reasonable for a proof of concept.&lt;/p&gt;

&lt;p&gt;However, production requirements introduce new challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do we authenticate users and enforce rate limits?&lt;/li&gt;
&lt;li&gt;What happens when the model provider becomes unavailable?&lt;/li&gt;
&lt;li&gt;How do we prevent expensive or repeated requests?&lt;/li&gt;
&lt;li&gt;How do we monitor latency, token usage, and failures?&lt;/li&gt;
&lt;li&gt;How do we retrieve reliable context for domain-specific questions?&lt;/li&gt;
&lt;li&gt;How do we protect sensitive data?&lt;/li&gt;
&lt;li&gt;How do we scale without making every request unnecessarily expensive?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A production architecture may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Users / Applications
                          |
                          v
                 API Gateway / Ingress
                          |
                          v
                  Application Backend
                      (FastAPI)
                          |
               +----------+----------+
               |          |          |
               v          v          v
             Cache    AI Workflow  Rate Limits
                          |
               +----------+----------+
               |                     |
               v                     v
          Retrieval Layer        Model Gateway
               |                     |
               v               +-----+-----+
         Vector / Search        |     |     |
            Database            v     v     v
                              Model Model  Local
                                A     B    LLM

        Cross-cutting infrastructure:
        Identity | Secrets | Telemetry | Queues
        Security | Evaluation | Cost Controls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a reference architecture, not a requirement to deploy every component. A smaller application may need only a backend, a model API, basic observability, and sensible limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The principle: Let requirements drive architectural complexity.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The API Layer: Your First Production Boundary
&lt;/h2&gt;

&lt;p&gt;The backend is more than a wrapper around a model API.&lt;/p&gt;

&lt;p&gt;It is the boundary that enforces application-level rules.&lt;/p&gt;

&lt;p&gt;A production API should consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication and authorization&lt;/li&gt;
&lt;li&gt;Request validation&lt;/li&gt;
&lt;li&gt;Input and output size limits&lt;/li&gt;
&lt;li&gt;Timeouts and concurrency limits&lt;/li&gt;
&lt;li&gt;Rate limiting and quotas&lt;/li&gt;
&lt;li&gt;Structured error responses&lt;/li&gt;
&lt;li&gt;Request IDs and tracing&lt;/li&gt;
&lt;li&gt;Protection against abuse and resource exhaustion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a FastAPI endpoint that calls an LLM should not wait indefinitely for a provider response.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nf"&gt;call_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&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;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AI service timed out&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;call_llm()&lt;/code&gt; represents an asynchronous model-client function implemented separately.&lt;/p&gt;

&lt;p&gt;This example demonstrates request validation and a timeout. A real deployment also needs authentication, rate limiting, safe exception handling, provider-specific error mapping, and appropriate concurrency controls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural lesson:&lt;/strong&gt; Never allow an external model dependency to control the availability of your entire application.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Model Gateway: Decouple Your Application From Model Providers
&lt;/h2&gt;

&lt;p&gt;Suppose your application directly integrates with one model provider.&lt;/p&gt;

&lt;p&gt;Over time, you may want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switch to a cheaper model for simple requests.&lt;/li&gt;
&lt;li&gt;Use a more capable model for complex reasoning.&lt;/li&gt;
&lt;li&gt;Route sensitive workloads to an approved deployment.&lt;/li&gt;
&lt;li&gt;Apply consistent rate limits across applications.&lt;/li&gt;
&lt;li&gt;Track token consumption by team or customer.&lt;/li&gt;
&lt;li&gt;Fail over when a provider experiences an outage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If every application implements these capabilities independently, complexity spreads throughout your codebase.&lt;/p&gt;

&lt;p&gt;A model gateway provides a centralized integration boundary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
     |
     v
Model Gateway
     |
     +----&amp;gt; Model Provider A
     |
     +----&amp;gt; Model Provider B
     |
     +----&amp;gt; Self-hosted Model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gateway can centralize provider adapters, authentication, usage tracking, routing policies, and selected resilience mechanisms.&lt;/p&gt;

&lt;p&gt;However, a gateway does not automatically solve every reliability problem. Failover requires compatible models, explicit policies, sufficient capacity, and appropriate handling of data and privacy constraints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural lesson:&lt;/strong&gt; Introduce an abstraction when it reduces meaningful coupling—not simply because it is a popular pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. RAG Infrastructure: Retrieval Quality Is Not Answer Quality
&lt;/h2&gt;

&lt;p&gt;For applications using Retrieval-Augmented Generation (RAG), the model is only one part of the pipeline.&lt;/p&gt;

&lt;p&gt;A typical request flows through:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Query processing&lt;/li&gt;
&lt;li&gt;Document retrieval&lt;/li&gt;
&lt;li&gt;Optional reranking&lt;/li&gt;
&lt;li&gt;Context construction&lt;/li&gt;
&lt;li&gt;Prompt assembly&lt;/li&gt;
&lt;li&gt;Model inference&lt;/li&gt;
&lt;li&gt;Response validation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each stage can introduce errors.&lt;/p&gt;

&lt;p&gt;Imagine a support assistant retrieves the correct refund policy but produces an incorrect answer.&lt;/p&gt;

&lt;p&gt;The problem might be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Relevant information was truncated during context construction.&lt;/li&gt;
&lt;li&gt;Conflicting documents were included.&lt;/li&gt;
&lt;li&gt;The prompt failed to distinguish authoritative policy from other context.&lt;/li&gt;
&lt;li&gt;The model ignored or misinterpreted the retrieved evidence.&lt;/li&gt;
&lt;li&gt;The response was not checked against the source material.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding a more powerful model may not fix any of these issues.&lt;/p&gt;

&lt;p&gt;A production RAG system should evaluate retrieval quality and answer quality separately.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;What it tells us&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Retrieval quality&lt;/td&gt;
&lt;td&gt;Did we retrieve the right evidence?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Groundedness&lt;/td&gt;
&lt;td&gt;Is the answer supported by the supplied evidence?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Answer relevance&lt;/td&gt;
&lt;td&gt;Does the answer address the question?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;How long does each pipeline stage take?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;How many tokens and retrieval operations were required?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Architectural lesson:&lt;/strong&gt; Treat RAG as a multi-stage information-processing system, not as a single LLM call with a vector database attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Caching, Queues, and Backpressure: Protect the System Under Load
&lt;/h2&gt;

&lt;p&gt;A prototype may process requests without any noticeable problems.&lt;/p&gt;

&lt;p&gt;Production traffic is different.&lt;/p&gt;

&lt;p&gt;A sudden spike in requests can exhaust application workers, overwhelm a model provider, increase queueing delays, and trigger expensive retry storms.&lt;/p&gt;

&lt;p&gt;Three infrastructure patterns help address these problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caching
&lt;/h3&gt;

&lt;p&gt;Cache results when requests are sufficiently repeatable and the semantics allow reuse.&lt;/p&gt;

&lt;p&gt;Potential candidates include stable embeddings, repeated retrieval results, and selected responses.&lt;/p&gt;

&lt;p&gt;Be careful with user-specific data, authorization boundaries, changing source documents, and sensitive information. A cache must not return one user's data to another.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queues and Asynchronous Processing
&lt;/h3&gt;

&lt;p&gt;Not every AI operation needs to finish during the original HTTP request.&lt;/p&gt;

&lt;p&gt;Long-running document processing, batch inference, and multi-step workflows may benefit from a queue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  |
  v
API ---&amp;gt; Queue ---&amp;gt; Worker ---&amp;gt; Model / Retrieval
          |                       |
          +---- Job Status &amp;lt;------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API can acknowledge the job while a worker processes it.&lt;/p&gt;

&lt;p&gt;This introduces additional requirements: durable job state, idempotency, retry policies, dead-letter handling, and a way for clients to retrieve results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backpressure
&lt;/h3&gt;

&lt;p&gt;When downstream services cannot keep up, the application needs to limit incoming work rather than accept unlimited requests.&lt;/p&gt;

&lt;p&gt;Depending on the workload, that might mean bounded queues, concurrency limits, admission control, or explicit overload responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural lesson:&lt;/strong&gt; Scaling is not simply adding more workers. You must also control how work enters the system and how much downstream capacity is available.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Observability: Know Why the System Is Failing
&lt;/h2&gt;

&lt;p&gt;Traditional application monitoring is necessary, but AI systems need additional visibility.&lt;/p&gt;

&lt;p&gt;Consider a user reporting that an answer took 12 seconds to generate.&lt;/p&gt;

&lt;p&gt;Was the delay caused by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API queueing?&lt;/li&gt;
&lt;li&gt;A slow vector database?&lt;/li&gt;
&lt;li&gt;Reranking?&lt;/li&gt;
&lt;li&gt;Model-provider latency?&lt;/li&gt;
&lt;li&gt;A large prompt?&lt;/li&gt;
&lt;li&gt;Repeated tool calls?&lt;/li&gt;
&lt;li&gt;An overloaded inference server?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without stage-level telemetry, these possibilities are difficult to distinguish.&lt;/p&gt;

&lt;p&gt;A useful observability strategy combines three categories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure Metrics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;CPU and memory utilization&lt;/li&gt;
&lt;li&gt;Pod restarts and resource throttling&lt;/li&gt;
&lt;li&gt;Queue depth and worker saturation&lt;/li&gt;
&lt;li&gt;GPU utilization and memory pressure, when self-hosting&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Application Metrics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Request volume and error rates&lt;/li&gt;
&lt;li&gt;End-to-end and stage-level latency&lt;/li&gt;
&lt;li&gt;Timeout and retry rates&lt;/li&gt;
&lt;li&gt;Cache hit rates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AI-Specific Metrics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Input and output token counts&lt;/li&gt;
&lt;li&gt;Time to first token and generation throughput&lt;/li&gt;
&lt;li&gt;Model selection and fallback frequency&lt;/li&gt;
&lt;li&gt;Retrieval and evaluation scores&lt;/li&gt;
&lt;li&gt;Cost per request or successful task&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Distributed tracing can connect a single request across the API, retrieval service, model gateway, and downstream tools.&lt;/p&gt;

&lt;p&gt;OpenTelemetry is useful for instrumenting traces, metrics, and logs across these boundaries. Grafana and compatible telemetry backends can then help engineers investigate system behavior.&lt;/p&gt;

&lt;p&gt;Be careful with telemetry that contains prompts, retrieved documents, credentials, or personal information. Observability must be designed with data minimization and access controls in mind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural lesson:&lt;/strong&gt; If you cannot identify which component caused a latency or quality regression, you cannot reliably optimize the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Security Is Part of the Architecture—Not a Final Checklist
&lt;/h2&gt;

&lt;p&gt;AI applications introduce risks beyond ordinary API security.&lt;/p&gt;

&lt;p&gt;Consider an agent that can retrieve internal documents and execute tools.&lt;/p&gt;

&lt;p&gt;A malicious prompt might attempt to influence the agent into accessing unauthorized data or performing an unintended action.&lt;/p&gt;

&lt;p&gt;The model's instructions alone are not a security boundary.&lt;/p&gt;

&lt;p&gt;A production design should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication and authorization enforced outside the model&lt;/li&gt;
&lt;li&gt;Least-privilege identities for tools and services&lt;/li&gt;
&lt;li&gt;Server-side validation of tool arguments&lt;/li&gt;
&lt;li&gt;Network and execution boundaries for untrusted workloads&lt;/li&gt;
&lt;li&gt;Secret management rather than credentials in prompts&lt;/li&gt;
&lt;li&gt;Approval gates for sensitive or irreversible operations&lt;/li&gt;
&lt;li&gt;Audit trails for important actions&lt;/li&gt;
&lt;li&gt;Protection against prompt injection and unsafe data flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if an AI assistant can issue a refund, the backend must independently verify that the user is authorized and that the transaction satisfies business rules.&lt;/p&gt;

&lt;p&gt;The model can recommend an action. The application must enforce whether that action is permitted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural lesson:&lt;/strong&gt; Treat model output as untrusted input, even when the model is powerful and the prompt is carefully designed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7cib3j9g0egvps11veis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7cib3j9g0egvps11veis.png" alt="architecture-image" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Self-Hosted Models or Managed APIs?
&lt;/h2&gt;

&lt;p&gt;This is one of the most consequential infrastructure decisions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Consideration&lt;/th&gt;
&lt;th&gt;Managed Model API&lt;/th&gt;
&lt;th&gt;Self-Hosted Inference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Initial setup&lt;/td&gt;
&lt;td&gt;Usually simpler&lt;/td&gt;
&lt;td&gt;More infrastructure work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operational burden&lt;/td&gt;
&lt;td&gt;Lower infrastructure burden&lt;/td&gt;
&lt;td&gt;Model serving, capacity, and GPU operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Provider-managed, subject to limits&lt;/td&gt;
&lt;td&gt;Your responsibility or your platform provider's&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model control&lt;/td&gt;
&lt;td&gt;Depends on provider&lt;/td&gt;
&lt;td&gt;Greater control over serving and configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost structure&lt;/td&gt;
&lt;td&gt;Usage-based or contracted&lt;/td&gt;
&lt;td&gt;Infrastructure, capacity, and operational costs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data requirements&lt;/td&gt;
&lt;td&gt;Depends on provider and contract&lt;/td&gt;
&lt;td&gt;Can offer greater deployment control, but requires secure operations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Self-hosting does not automatically make inference cheaper or more private. GPU utilization, model size, traffic patterns, staffing, network boundaries, and operational maturity all matter.&lt;/p&gt;

&lt;p&gt;Similarly, managed APIs do not eliminate reliability engineering: rate limits, provider outages, latency, quotas, and contractual requirements still affect your application.&lt;/p&gt;

&lt;p&gt;A hybrid architecture can make sense when different workloads have different requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The right decision depends on workload characteristics and measurable constraints—not a blanket preference for managed or self-hosted models.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Production-Readiness Checklist
&lt;/h2&gt;

&lt;p&gt;Before deploying an LLM-powered application, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Are authentication, authorization, and request limits enforced?&lt;/li&gt;
&lt;li&gt;[ ] Do model calls have bounded timeouts and explicit failure behavior?&lt;/li&gt;
&lt;li&gt;[ ] Are retries bounded and safe to repeat?&lt;/li&gt;
&lt;li&gt;[ ] Have we tested provider failures and overload conditions?&lt;/li&gt;
&lt;li&gt;[ ] Do we evaluate retrieval quality and answer quality separately?&lt;/li&gt;
&lt;li&gt;[ ] Can we trace a request across all important components?&lt;/li&gt;
&lt;li&gt;[ ] Are token usage, latency, and cost measured?&lt;/li&gt;
&lt;li&gt;[ ] Is sensitive data handled safely in prompts, logs, and caches?&lt;/li&gt;
&lt;li&gt;[ ] Can we deploy and roll back changes safely?&lt;/li&gt;
&lt;li&gt;[ ] Do we have evaluation tests for important application behavior?&lt;/li&gt;
&lt;li&gt;[ ] Are capacity limits and degraded operating modes documented?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not every application needs a dedicated queue, multiple model providers, or a Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;The checklist is a way to identify risks and choose appropriate controls based on actual requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The biggest shift in moving an LLM application from prototype to production is a change in the engineering problem.&lt;/p&gt;

&lt;p&gt;Initially, the question is:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Can we make the model produce a useful response?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In production, the questions become:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Can we produce useful responses reliably, securely, observably, and at a sustainable cost—even when dependencies fail and traffic changes?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That requires thinking across application architecture, infrastructure, data, security, and operations.&lt;/p&gt;

&lt;p&gt;The model is an important component. It is not the entire system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real engineering work begins when the demo succeeds and the system has to keep succeeding.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's Discuss
&lt;/h2&gt;

&lt;p&gt;If you were designing a production AI platform today, which concern would you prioritize first?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reliability and failure recovery&lt;/li&gt;
&lt;li&gt;Inference cost and latency&lt;/li&gt;
&lt;li&gt;Security and governance&lt;/li&gt;
&lt;li&gt;Evaluation and answer quality&lt;/li&gt;
&lt;li&gt;Scaling and infrastructure capacity&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'd be interested in hearing how other engineers are approaching these trade-offs in their own systems.&lt;/p&gt;

&lt;h1&gt;
  
  
  AI #LLMOps #SoftwareArchitecture #Kubernetes #SystemDesign #GenerativeAI #CloudComputing
&lt;/h1&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Batch Deploying GBase Database with a Response File</title>
      <dc:creator>mmllllzcn</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:20:33 +0000</pubDate>
      <link>https://dev.to/mmllllzcn/batch-deploying-gbase-database-with-a-response-file-ngn</link>
      <guid>https://dev.to/mmllllzcn/batch-deploying-gbase-database-with-a-response-file-ngn</guid>
      <description>&lt;p&gt;Deploying GBase Database(GBase 8s) across multiple servers can become repetitive if every installation requires manual interaction. A response file provides a more consistent approach by defining installation parameters once and reusing them across deployment targets.&lt;/p&gt;

&lt;p&gt;Combined with shell scripts and remote execution tools such as SSH, silent installation can become part of a repeatable database deployment workflow.&lt;/p&gt;

&lt;p&gt;The goal is not simply to install software faster. It is to make the deployment process easier to validate, maintain, and reproduce.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Prepare the Response File
&lt;/h2&gt;

&lt;p&gt;Start by preparing a &lt;code&gt;bundle.properties&lt;/code&gt; response file using the template supplied with your GBase Database installation media or by recording a supported interactive installation.&lt;/p&gt;

&lt;p&gt;Review the installation directory, selected components, license-acceptance settings, and any other required parameters.&lt;/p&gt;

&lt;p&gt;Before deploying to multiple machines, test the response file on one representative server. Confirm that the installation succeeds and that the resulting database instance can be configured and started correctly.&lt;/p&gt;

&lt;p&gt;Keep environment-specific settings separate where necessary. Different servers may require different paths, resource configurations, or connection definitions.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Create a Reusable Installation Script
&lt;/h2&gt;

&lt;p&gt;The following Bash example checks for the response file and installer before starting silent installation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-Eeuo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;RESP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/bundle.properties"&lt;/span&gt;
&lt;span class="nv"&gt;INSTALLER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/ids_install"&lt;/span&gt;

&lt;span class="c"&gt;# Verify required files&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: Missing response file: &lt;/span&gt;&lt;span class="nv"&gt;$RESP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INSTALLER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: Installer is missing or not executable: &lt;/span&gt;&lt;span class="nv"&gt;$INSTALLER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Run silent installation&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INSTALLER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; silent &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-DLICENSE_ACCEPTED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TRUE

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Silent installation command completed."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the script as &lt;code&gt;install_gbase.sh&lt;/code&gt; and make it executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x install_gbase.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script uses &lt;code&gt;set -Eeuo pipefail&lt;/code&gt; to help detect command failures and prevent certain errors from being silently ignored.&lt;/p&gt;

&lt;p&gt;The installer command shown here is an example. Confirm its supported options and license-acceptance requirements against the documentation for your specific installation package.&lt;/p&gt;

&lt;p&gt;Also note that a successful installer exit does not necessarily mean the database instance is ready for application connections. Post-installation configuration and validation remain necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Execute the Installation on Multiple Servers
&lt;/h2&gt;

&lt;p&gt;For a small environment, SSH and SCP can distribute the response file and execute the installation script remotely.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-Eeuo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;RESP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/bundle.properties"&lt;/span&gt;
&lt;span class="nv"&gt;SCRIPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/install_gbase.sh"&lt;/span&gt;
&lt;span class="nv"&gt;HOSTS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;node1 node2 node3&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: Missing response file"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SCRIPT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: Missing installation script"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;host &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOSTS&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&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="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Deploying to &lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;

    scp &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SCRIPT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"root@&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;:/opt/"&lt;/span&gt;

    ssh &lt;span class="s2"&gt;"root@&lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="s2"&gt;"bash /opt/install_gbase.sh"&lt;/span&gt;

    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Installation command completed on &lt;/span&gt;&lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example processes the hosts sequentially, which makes execution easier to follow and failures easier to isolate.&lt;/p&gt;

&lt;p&gt;Before using it, ensure that SSH authentication, host-key verification, remote permissions, and installation prerequisites are configured correctly.&lt;/p&gt;

&lt;p&gt;The example also assumes that the installer is already available at &lt;code&gt;/opt/ids_install&lt;/code&gt; on every target server. If it is not, include distribution and verification of the installation media in the deployment workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Running the installer as &lt;code&gt;root&lt;/code&gt; should follow the supported installation procedure. Instance initialization may require switching to the designated database account, such as &lt;code&gt;gbasedbt&lt;/code&gt;. Do not assume installation and initialization use the same account.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Add Post-Installation Configuration and Validation
&lt;/h2&gt;

&lt;p&gt;Batch installation should not stop when the installer exits.&lt;/p&gt;

&lt;p&gt;A complete GBase Database deployment workflow should also address:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Environment variables:&lt;/strong&gt; Configure the required instance-specific environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration files:&lt;/strong&gt; Review &lt;code&gt;onconfig&lt;/code&gt; and &lt;code&gt;sqlhosts&lt;/code&gt; for the target server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instance initialization:&lt;/strong&gt; Run the documented initialization procedure using the appropriate operating system account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service validation:&lt;/strong&gt; Confirm that the instance starts and client connections succeed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging:&lt;/strong&gt; Record the host, installation result, and relevant diagnostic output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These steps should be implemented according to the selected installation method and the requirements of your environment.&lt;/p&gt;

&lt;p&gt;Avoid reporting a server as successfully deployed until the required validation checks have passed.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Make Batch Deployment Reproducible
&lt;/h2&gt;

&lt;p&gt;For larger deployments, consider a few additional safeguards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version-control the response file and scripts.&lt;/strong&gt; Review changes before they are used in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protect sensitive configuration.&lt;/strong&gt; Do not store passwords, private keys, or other secrets in ordinary response files or source repositories.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Control deployment concurrency.&lt;/strong&gt; Start with sequential execution. Introduce parallel deployment only after validating resource requirements and failure handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make reruns safe.&lt;/strong&gt; Check whether a target already has an installation or running instance before executing the installer again. A failed deployment should not automatically trigger an unsafe reinstall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintain a recovery procedure.&lt;/strong&gt; Record installation logs and define how failed or partially completed deployments will be investigated and corrected.&lt;/p&gt;

&lt;p&gt;For larger fleets, configuration-management tools can provide better inventory management, controlled concurrency, and centralized execution reporting than a collection of ad hoc shell commands.&lt;/p&gt;

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

&lt;p&gt;Batch deployment of GBase Database(GBase 8s) starts with a validated response file and a reliable installation script. SSH and SCP can extend that workflow across multiple machines, while configuration management and automated checks help maintain consistency as the environment grows.&lt;/p&gt;

&lt;p&gt;The essential principle is simple: standardize the installation inputs, validate the results, and make failures visible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does your team manage database deployments across multiple servers: shell scripts, SSH automation, or a configuration-management platform?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>gbase</category>
      <category>database</category>
      <category>数据库</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>auditsift: security-audit every PR with an AI agent, without drowning in false positives</title>
      <dc:creator>bayuh85</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:20:19 +0000</pubDate>
      <link>https://dev.to/bayuh85/auditsift-security-audit-every-pr-with-an-ai-agent-without-drowning-in-false-positives-24cm</link>
      <guid>https://dev.to/bayuh85/auditsift-security-audit-every-pr-with-an-ai-agent-without-drowning-in-false-positives-24cm</guid>
      <description>&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; 👉 &lt;a href="https://github.com/bayuh85/auditsift" rel="noopener noreferrer"&gt;github.com/bayuh85/auditsift&lt;/a&gt; (MIT, Python 3.10+, 64 offline tests)&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Ask a coding agent to "review this PR for security issues" and you usually get back a long, confident list. Some of it is real. A lot of it is plausible-sounding noise: an "SQL injection" in a query that is already parameterized, a "hardcoded secret" that is a test fixture, severities that change every time you ask. There is no file and line you can trust, and nothing your CI can parse.&lt;/p&gt;

&lt;p&gt;Then there is the bill. Running a flagship model over every pull request "just in case" is the kind of habit teams drop after the first invoice.&lt;/p&gt;

&lt;p&gt;What I wanted was boring and reliable: cheap offline checks first, a model only for the handful of candidates worth explaining, and a second opinion that has to quote the actual code before a finding counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What auditsift does
&lt;/h2&gt;

&lt;p&gt;auditsift runs an audit in phases. The first two need no API key and never touch the network.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Needs a key?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;map&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Languages, entry points, routes, dependency manifests, redacted secret-like strings&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Offline rules: hardcoded secrets, SQL built by string interpolation, &lt;code&gt;shell=True&lt;/code&gt; / &lt;code&gt;os.system&lt;/code&gt; with input, &lt;code&gt;eval&lt;/code&gt;/&lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;pickle&lt;/code&gt; and &lt;code&gt;yaml.load&lt;/code&gt;, weak hashes, &lt;code&gt;verify=False&lt;/code&gt;, path traversal, SSRF, permissive CORS&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;triage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sends only the candidate snippet to a model for impact and a fix&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;verify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A second, independent model must confirm or reject each candidate, quoting a snippet that really exists in the file&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;report&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JSON, SARIF 2.1.0 and a short Markdown summary, plus &lt;code&gt;--fail-on high&lt;/code&gt; for CI&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The verify step is the part I care about most. It doesn't see the triage write-up, so it can't just agree with it. A "confirmed" verdict only counts when the quoted evidence is found in the code. Rejected findings are dropped from SARIF and counted in the summary, so you can see how much noise was removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real use case: gate a Flask PR in CI
&lt;/h2&gt;

&lt;p&gt;The repo ships a deliberately broken sample app in &lt;code&gt;examples/flask-notes&lt;/code&gt;. Here is the offline scan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[dev]"&lt;/span&gt;
auditsift scan examples/flask-notes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;auditsift  examples/flask-notes
13 findings kept, 0 rejected

high     AS-SQLI     examples/flask-notes/app.py:28  SQL query is built with string interpolation.
          impact: An attacker who controls the interpolated value can change the query.
          fix: Use a parameterized query and pass values as bound parameters.
high     AS-DESER    examples/flask-notes/app.py:41  Untrusted data can reach yaml.load.
          fix: Use yaml.safe_load, or a format such as JSON that does not construct objects.
high     AS-SSRF     examples/flask-notes/app.py:53  Outbound request target includes external input (target).
high     AS-TLS      examples/flask-notes/app.py:53  TLS certificate verification is disabled.
high     AS-SHELL    examples/flask-notes/app.py:60  Shell command is built from external input.
...
cost estimate
  map      local    0 in /  0 out  (0 calls)
  scan     local    0 in /  0 out  (0 calls)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Secrets are redacted before they are printed, written to a report or sent to a model.&lt;/p&gt;

&lt;p&gt;For a pull request you only care about the lines that changed, so &lt;code&gt;--diff&lt;/code&gt; filters findings to the &lt;code&gt;git diff&lt;/code&gt; against a ref:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;auditsift audit &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--diff&lt;/span&gt; origin/main &lt;span class="nt"&gt;--fail-on&lt;/span&gt; high &lt;span class="nt"&gt;--out&lt;/span&gt; results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code &lt;code&gt;1&lt;/code&gt; means a kept finding is at or above the gate, so CI blocks the merge. &lt;code&gt;results/&lt;/code&gt; holds &lt;code&gt;auditsift.json&lt;/code&gt;, &lt;code&gt;auditsift.sarif&lt;/code&gt; and &lt;code&gt;auditsift.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The GitHub Actions job is short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auditsift sarif&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;security-events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;audit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install .&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="s"&gt;auditsift audit . --diff origin/${{ github.base_ref }}&lt;/span&gt;
          &lt;span class="s"&gt;--offline --fail-on high --out results&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/upload-sarif@v3&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;results/auditsift.sarif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--offline&lt;/code&gt; keeps the required check free. When you want explanations and a second opinion on the diff, add your key as a repo secret and drop &lt;code&gt;--offline&lt;/code&gt;. Findings show up in the Security tab like any other code-scanning tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding the models without the flagship bill
&lt;/h2&gt;

&lt;p&gt;auditsift keeps the model work small on purpose. The scan narrows a whole repo down to a few snippets, and only those go out. &lt;code&gt;--max-llm&lt;/code&gt; caps how many candidates are sent at all. Every run prints a token ledger per phase, so you can see exactly what triage and verify cost.&lt;/p&gt;

&lt;p&gt;The default endpoint is &lt;a href="https://atmorouter.dev" rel="noopener noreferrer"&gt;AtmoRouter&lt;/a&gt;, a cheap AI API gateway and OpenRouter alternative. That matters here because the setup wants two different models through one key: a decent triage model and a cheaper verify model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ATMOROUTER_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ar-your-key-here
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AUDITSIFT_TRIAGE_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openai/gpt-4o-mini
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AUDITSIFT_VERIFY_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;openai/gpt-4.1-nano
auditsift audit examples/flask-notes &lt;span class="nt"&gt;--fail-on&lt;/span&gt; high &lt;span class="nt"&gt;--out&lt;/span&gt; results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AtmoRouter gives you 109 models behind one OpenAI- and Anthropic-compatible key, pay per token, at an average of 83.7% below official prices. Running two passes on every PR stops being a budget conversation. If you'd rather use another provider, set &lt;code&gt;AUDITSIFT_BASE_URL&lt;/code&gt; and &lt;code&gt;AUDITSIFT_API_KEY&lt;/code&gt; to any OpenAI-compatible endpoint, or &lt;code&gt;AUDITSIFT_API_STYLE=anthropic&lt;/code&gt; for &lt;code&gt;/v1/messages&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use it from Cursor or Claude Code
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;auditsift serve&lt;/code&gt; is an MCP server over stdio with &lt;code&gt;auditsift_map&lt;/code&gt;, &lt;code&gt;auditsift_scan&lt;/code&gt; and &lt;code&gt;auditsift_audit&lt;/code&gt;. Map and scan stay offline, and &lt;code&gt;auditsift_audit&lt;/code&gt; only calls models when the agent passes &lt;code&gt;use_llm: true&lt;/code&gt;, so an agent doesn't spend tokens by accident.&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;"mcpServers"&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="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"auditsift"&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="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"auditsift"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"serve"&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="w"&gt;
  &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="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add auditsift &lt;span class="nt"&gt;--&lt;/span&gt; auditsift serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good agent loop: scan the diff, read the candidates, and only ask for the paid audit on the ones that look real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Code: &lt;a href="https://github.com/bayuh85/auditsift" rel="noopener noreferrer"&gt;github.com/bayuh85/auditsift&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Gateway used by default: &lt;a href="https://atmorouter.dev" rel="noopener noreferrer"&gt;atmorouter.dev&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's MIT licensed and the rule set is small and readable, so adding a rule for your own framework is a short PR. If it catches something real in your repo, a star on GitHub helps other people find it.&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>How to Give Claude &amp; Cursor Safe Database Access Without Crashing Production (Zero-Trust MCP Guard in Python)</title>
      <dc:creator>johann</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:20:15 +0000</pubDate>
      <link>https://dev.to/johann_schiffer/how-to-give-claude-cursor-safe-database-access-without-crashing-production-zero-trust-mcp-guard-45mm</link>
      <guid>https://dev.to/johann_schiffer/how-to-give-claude-cursor-safe-database-access-without-crashing-production-zero-trust-mcp-guard-45mm</guid>
      <description>&lt;h1&gt;
  
  
  How to Give Claude &amp;amp; Cursor Safe Database Access Without Crashing Production (Zero-Trust MCP Guard in Python)
&lt;/h1&gt;

&lt;p&gt;Giving an AI assistant or autonomous agent access to a live database is one of the most powerful workflows for modern software teams: Claude Desktop, Cursor, or Windsurf can write complex analytics queries, optimize slow joins, and inspect schemas in seconds.&lt;/p&gt;

&lt;p&gt;However, granting raw database credentials to an LLM is dangerous:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accidental Disasters:&lt;/strong&gt; One misaligned prompt or prompt injection can lead the agent to execute &lt;code&gt;DROP TABLE&lt;/code&gt;, &lt;code&gt;TRUNCATE&lt;/code&gt;, or unconstrained &lt;code&gt;UPDATE&lt;/code&gt; / &lt;code&gt;DELETE&lt;/code&gt; queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production Freezes:&lt;/strong&gt; A query missing a &lt;code&gt;WHERE&lt;/code&gt; clause or missing an index on a million-row table can trigger a full sequential table scan, consuming 100% CPU and locking transactions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PII Exposure:&lt;/strong&gt; Raw customer records (plaintext emails, password hashes, credit cards) get pulled into prompt contexts and third-party LLM server logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The modern, standardized solution is to place a &lt;strong&gt;Zero-Trust Model Context Protocol (MCP)&lt;/strong&gt; guard between your agent and the database engine.&lt;/p&gt;

&lt;p&gt;In this guide, we will walk through how &lt;strong&gt;PgSafe MCP&lt;/strong&gt; was engineered in Python to provide secure, read-only database exploration, forced &lt;code&gt;LIMIT&lt;/code&gt; injection, real-time PII redaction, and dry-run mutation simulation.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Zero-Trust MCP Architecture
&lt;/h2&gt;

&lt;p&gt;Rather than connecting Claude directly to Postgres or SQLite with an unrestricted superuser connection, the MCP server acts as an intelligent firewall:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ AI Agent: Claude / Cursor / Antigravity ]
                      │  JSON-RPC 2.0 (stdio)
                      ▼
             [ PgSafe MCP Server ]
                      │
           [ ReadOnlyGuard (AST Analysis) ]
           ├─ Blocks DROP, TRUNCATE, DELETE, ALTER
           └─ Blocks Semicolon Chaining
                      │
           [ CostAnalyzer (LIMIT Guard) ]
           ├─ Forces max 1,000 row limits
           └─ Warns on missing WHERE clauses
                      │
            [ Database Connection ]
            (PostgreSQL / SQLite / MySQL)
                      │
             [ Real-Time PII Masker ]
           ├─ Redacts emails (u***@domain.com)
           ├─ Masks credit cards (****-****-****-1234)
           └─ Redacts password hashes
                      │
                      ▼
[ Clean, Safe JSON-RPC Output to Agent ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  1. Enforcing Read-Only SQL Lexical Validation
&lt;/h2&gt;

&lt;p&gt;A secure read-only guard must inspect every statement before it ever reaches the database cursor, stripping comments and blocking destructive keywords:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;BLOCKED_KEYWORDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bDROP\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bTRUNCATE\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bDELETE\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bUPDATE\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bINSERT\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bALTER\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bCREATE\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bGRANT\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bREVOKE\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReadOnlyGuard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&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;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bool&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="c1"&gt;# Disallow multi-statement injection (semicolon chaining)
&lt;/span&gt;        &lt;span class="n"&gt;statements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statements&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;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&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;Semicolon chaining disallowed.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="c1"&gt;# Verify query begins with read-only keyword
&lt;/span&gt;        &lt;span class="n"&gt;first_word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statements&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="nf"&gt;split&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="nf"&gt;upper&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;first_word&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT&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;EXPLAIN&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;WITH&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;PRAGMA&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;SHOW&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&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;Statement &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;first_word&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; is forbidden.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BLOCKED_KEYWORDS&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;re&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;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;return&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;Destructive SQL operation detected.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&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 verified safe.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Dynamic LIMIT Injection
&lt;/h2&gt;

&lt;p&gt;Runaway &lt;code&gt;SELECT * FROM audit_logs&lt;/code&gt; queries can crash your client's memory. PgSafe intercepts every query and caps row limits:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;enforce_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&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;default_limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&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="n"&gt;cleaned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&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="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&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="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bLIMIT\s+(\d+)\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&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;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;limit_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;limit_val&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;max_limit&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;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bLIMIT\s+\d+\b&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;LIMIT &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;max_limit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cleaned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&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;cleaned&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;cleaned&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; LIMIT &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;default_limit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Real-Time PII Redaction Before Agent Ingestion
&lt;/h2&gt;

&lt;p&gt;When the query returns data, sensitive columns (&lt;code&gt;password_hash&lt;/code&gt;, &lt;code&gt;ssn&lt;/code&gt;, &lt;code&gt;token&lt;/code&gt;) and customer PII are redacted on the fly:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;EMAIL_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b([A-Za-z0-9._%+-])[A-Za-z0-9._%+-]*@([A-Za-z0-9.-]+\.[A-Z|a-z]{2,})\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;CARD_REGEX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(?:\d{4}[-\s]?){3}(\d{4})\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;redact_pii&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&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="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CARD_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;****-****-****-\1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EMAIL_REGEX&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\1***@\2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&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;text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Configuring Claude Desktop &amp;amp; Cursor
&lt;/h2&gt;

&lt;p&gt;Add the server to your &lt;code&gt;claude_desktop_config.json&lt;/code&gt;:&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;"mcpServers"&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="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"pgsafe"&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="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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;"-m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pgsafe.server"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/pgsafe-mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&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="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"DATABASE_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"postgresql://user:pass@localhost:5432/my_db"&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;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="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;Now you can prompt Claude:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Inspect the orders table, find the top 5 customers with pending invoices, and suggest indexes to speed up this query."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Enterprise Edition: Dry-Run Mutation Simulation Harness
&lt;/h2&gt;

&lt;p&gt;When agents need to draft data mutations or migrations, the Enterprise Pro edition includes an isolated simulation harness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Executes &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; queries within an isolated transaction.&lt;/li&gt;
&lt;li&gt;Returns row diffs, affected count, and execution time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guarantees auto-ROLLBACK:&lt;/strong&gt; changes are never committed to your database.&lt;/li&gt;
&lt;li&gt;Includes automatic index recommendation advisors and Docker Compose setups.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Downloads and Packages:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lite Version (Open Source)&lt;/strong&gt;: Read-only guard and SQLite/Postgres schema inspection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise Pro Edition ($14.99)&lt;/strong&gt;: PII masking, dry-run mutation simulation, index advisor, Docker Compose, and automated test suite.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://johanld.gumroad.com/l/bruhur" rel="noopener noreferrer"&gt;Get PgSafe Pro on Gumroad ($14.99)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>postgres</category>
      <category>ai</category>
      <category>database</category>
    </item>
    <item>
      <title>Jenkins at 3 and Kubernetes at 1: When a Small Count Means a Missing Signature</title>
      <dc:creator>StarkMan</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:20:10 +0000</pubDate>
      <link>https://dev.to/stark_zhuang_df5076f35c68/jenkins-at-3-and-kubernetes-at-1-when-a-small-count-means-a-missing-signature-5fn4</link>
      <guid>https://dev.to/stark_zhuang_df5076f35c68/jenkins-at-3-and-kubernetes-at-1-when-a-small-count-means-a-missing-signature-5fn4</guid>
      <description>&lt;h1&gt;
  
  
  Jenkins at 3 and Kubernetes at 1: When a Small Count Means a Missing Signature
&lt;/h1&gt;

&lt;p&gt;A vulnerability report that cites "only three exposed Jenkins servers" would be treated as good news. In reality it is usually a report about fingerprint coverage, and the difference matters when that number is used to justify deprioritizing a platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurements
&lt;/h2&gt;

&lt;p&gt;On 20 October 2026, between 22:33 and 22:37 UTC, ZoomEye queries with &lt;code&gt;sub_type="all"&lt;/code&gt; and a page size of one record returned:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;Matched assets&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;app="Jenkins"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;app="Grafana"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;app="Kubernetes"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;app="SonarQube"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12,022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;app="Apache Tomcat"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;585,251&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;app="GitLab"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1,397,539&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Jenkins, Grafana and Kubernetes are all widely deployed platforms with substantial internet-facing populations. SonarQube, a far less ubiquitous tool, returns four orders of magnitude more matches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the numbers invert
&lt;/h2&gt;

&lt;p&gt;A product fingerprint requires a distinctive, stable, safely observable response. SonarQube emits a recognizable web interface with identifiable markers on a predictable path. That makes a reliable signature easy to write.&lt;/p&gt;

&lt;p&gt;Jenkins also has a distinctive interface, but it is frequently deployed behind reverse proxies that rewrite headers, customised with themes, or restricted to internal networks. Its detection may depend on a path or marker that is often absent.&lt;/p&gt;

&lt;p&gt;Kubernetes is not a single application. An API server, a dashboard, an ingress controller and kubelets are separate components with separate exposure profiles. A signature labelled for the platform may correspond to an extremely narrow detection condition, which produces a count that describes the condition rather than the deployment.&lt;/p&gt;

&lt;p&gt;Grafana's 22 is likely to reflect a specific authentication redirect or version marker that most deployments do not present to an unauthenticated scanner.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for prioritization
&lt;/h2&gt;

&lt;p&gt;If an exposure count is used to rank platforms by risk, a missing signature silently removes an entire product class from consideration. That is the failure mode to guard against.&lt;/p&gt;

&lt;p&gt;A practical check before trusting a low count: query a neighbouring form of the same product. If &lt;code&gt;app="Jenkins"&lt;/code&gt; returns 3, try a title query, a header query, or a path-specific marker. A large difference between related queries is evidence of a signature gap rather than a small population.&lt;/p&gt;

&lt;p&gt;A second check: compare against your own deployment reality. If your organization operates forty Jenkins instances, a global count of three is obviously not a measurement of the global population.&lt;/p&gt;

&lt;h2&gt;
  
  
  The platforms that do yield stable counts
&lt;/h2&gt;

&lt;p&gt;SonarQube at 12,022 and Tomcat at 585,251 are useful precisely because their signatures are stable. Jenkins and Kubernetes are not, and no amount of repeated querying changes that.&lt;/p&gt;

&lt;p&gt;The methodological consequence: build exposure arguments on products whose fingerprints are reliable, and use internal inventory for the rest. A scan can support the first case and cannot support the second.&lt;/p&gt;

&lt;p&gt;For a defender operating Jenkins, the useful external question is not "how many are exposed globally" but "is any instance of ours answering from a public address." That question is answered by scanning your own address space, and the answer is binary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CI/CD consequence
&lt;/h2&gt;

&lt;p&gt;This matters more than for a typical web application because CI/CD servers hold deployment credentials. A Jenkins instance with build credentials can modify production artifacts, which is a supply-chain position rather than a hosting position.&lt;/p&gt;

&lt;p&gt;For that reason, the appropriate default is that no CI/CD server answers from the public internet. Where a build agent must be reachable, it should be the agent, not the controller.&lt;/p&gt;

&lt;p&gt;An organization relying on an external count of "three exposed Jenkins servers globally" to conclude that its own instance is safe would be making an inference the data cannot support.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on the honesty of measurement
&lt;/h2&gt;

&lt;p&gt;Publishing a count of 3 for Jenkins without stating that signatures are incomplete would be technically accurate and practically misleading. The same applies to a count of 1 for Kubernetes.&lt;/p&gt;

&lt;p&gt;This series reports counts with their queries and treats implausible values as evidence about the tool rather than about the world. That discipline is not pedantry; it is the difference between a measurement and a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ZoomEye, &lt;a href="https://www.zoomeye.ai/" rel="noopener noreferrer"&gt;search interface&lt;/a&gt;, queried 20 October 2026 for the dorks listed above.&lt;/li&gt;
&lt;li&gt;Jenkins, &lt;a href="https://www.jenkins.io/security/advisories/" rel="noopener noreferrer"&gt;security advisory index&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;CISA, &lt;a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog" rel="noopener noreferrer"&gt;Known Exploited Vulnerabilities Catalog&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>zoomeye</category>
      <category>measurement</category>
      <category>devsecops</category>
    </item>
    <item>
      <title>Persist the practice stop before sending it</title>
      <dc:creator>Giang Pham Truong</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:19:59 +0000</pubDate>
      <link>https://dev.to/ptrgiang/persist-the-practice-stop-before-sending-it-pjj</link>
      <guid>https://dev.to/ptrgiang/persist-the-practice-stop-before-sending-it-pjj</guid>
      <description>&lt;p&gt;Ending a piano practice session sounds like one POST. The tricky case is a lost response: the server may have accepted the stop while the client still sees a timeout. Retrying with a new event ID could turn one user action into two logical events.&lt;/p&gt;

&lt;p&gt;In Pianory's account-backed practice flow, we persist the intent before sending it. queuePracticeStop writes both the session snapshot and the pending stop into one IndexedDB readwrite transaction. The pending record contains an ordered list of tick and stop events; each event keeps its ID and seconds. If a tick request had an uncertain response, we keep that same ID for replay.&lt;/p&gt;

&lt;p&gt;The replay path sends the events in order. It rejects a failed HTTP response, validates that the stop acknowledgement belongs to the same session and contains a nonnegative integer duration, and only then calls completePracticeStop. That final method updates the session and removes the pending record in another IndexedDB transaction.&lt;/p&gt;

&lt;p&gt;The useful invariant is small: an uncertain network result leaves a durable pending record. A later retry refers to the same logical event, rather than inventing a fresh one. The in-memory replay guard also coalesces concurrent sync requests within a client namespace.&lt;/p&gt;

&lt;p&gt;A focused test covers replay and retry ordering. The end-to-end check uses a mock endpoint. This does not prove live D1 behavior, exactly-once delivery, or that every elapsed second survives every possible device failure. The contract is limited to account-backed sessions; guest sessions do not queue server practice stops.&lt;/p&gt;

&lt;p&gt;This pattern applies beyond music practice: if the user says 'done', save that intent locally before treating the network as reliable.&lt;/p&gt;

&lt;p&gt;AI assisted with drafting this article; I checked these claims against the repository and focused tests. I make Pianory, the piano-learning app behind this example.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>offline</category>
      <category>testing</category>
    </item>
    <item>
      <title>Oltre il "No Fluff": la stratificazione semantica negli agenti di coding</title>
      <dc:creator>Luca Morricone</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:19:53 +0000</pubDate>
      <link>https://dev.to/luca_morricone/oltre-il-no-fluff-la-stratificazione-semantica-negli-agenti-di-coding-3cjm</link>
      <guid>https://dev.to/luca_morricone/oltre-il-no-fluff-la-stratificazione-semantica-negli-agenti-di-coding-3cjm</guid>
      <description>&lt;p&gt;&lt;strong&gt;Due prompt, quattro condizioni, otto sessioni: cosa cambia quando una regola di comunicazione incontra una funzione di Fibonacci.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;Se chiedi a un assistente IA:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who was Dante Alighieri?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ottieni una biografia. Aggiungi una piccola istruzione:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;no-fluff Who was Dante Alighieri?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nella mia prova, la risposta è diventata più breve. Fin qui, nulla di particolarmente sorprendente.&lt;/p&gt;

&lt;p&gt;Ora sostituisci la domanda con:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;no-fluff Develop a fibonacci function&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;L'agente dovrebbe scrivere una spiegazione più breve, una funzione più corta o una soluzione con meno funzionalità? Dovrebbe comunque creare file ed eseguire test?&lt;/p&gt;

&lt;p&gt;Nel mio &lt;a href="https://dev.to/luca_morricone/coding-senza-compiacenza-come-far-dire-no-agli-agenti-ia-31ie"&gt;precedente articolo sulla sicofanzia&lt;/a&gt;, ho esplorato come alcune istruzioni comportamentali potessero spingere gli agenti di coding a mettere in discussione le mie richieste, invece di assecondarle subito. Questa volta volevo indagare un altro confine: &lt;strong&gt;un'istruzione, che di primo acchito sembra riguardare la comunicazione tra utente e assistente, può influenzare anche il codice e perfino come un agente svolge il suo lavoro?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ho confrontato due richieste, discriminandole con quattro condizioni. Un totale di otto sessioni all'interno di un unico piccolo esperimento esplorativo, motivato solo dalla mia curiosità di capire: non un benchmark, né una misura controllata del comportamento di tutti i modelli.&lt;/p&gt;

&lt;p&gt;Le differenze non si sono fermate al numero di parole nelle risposte.&lt;/p&gt;




&lt;h2&gt;
  
  
  Indice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Oltre il "No Fluff": la stratificazione semantica negli agenti di coding

&lt;ul&gt;
&lt;li&gt;Indice&lt;/li&gt;
&lt;li&gt;1. L'esperimento: due richieste, quattro condizioni&lt;/li&gt;
&lt;li&gt;2. Dante: meno testo, ma anche una diversa selezione delle informazioni&lt;/li&gt;
&lt;li&gt;Cosa resta e cosa scompare&lt;/li&gt;
&lt;li&gt;3. Fibonacci: cambia anche ciò che l'agente consegna&lt;/li&gt;
&lt;li&gt;4. La funzione: non si tratta soltanto di togliere commenti&lt;/li&gt;
&lt;li&gt;5. La mia interpretazione: la stratificazione semantica negli LLM&lt;/li&gt;
&lt;li&gt;Discutiamone&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. L'esperimento: due richieste, quattro condizioni
&lt;/h2&gt;

&lt;p&gt;Sono partito dalle stesse due richieste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Who was Dante Alighieri?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Develop a fibonacci function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per ciascuna ho confrontato quattro condizioni:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Default:&lt;/strong&gt; nessuna istruzione aggiuntiva.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No-fluff:&lt;/strong&gt; il prefisso letterale &lt;code&gt;no-fluff&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caveman:&lt;/strong&gt; invocazione esplicita della skill &lt;code&gt;caveman&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-fluff:&lt;/strong&gt; invocazione esplicita di una skill minima di comunicazione.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nelle due condizioni con skill, le richieste di coding erano:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/skill:caveman Develop a fibonacci function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/skill:zero-fluff Develop a fibonacci function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le sessioni si sono svolte in Pi. Il modello usato era &lt;code&gt;gemini-3.8-flash-tiered&lt;/code&gt;. Gli export delle sessioni sono tutti consultabili su &lt;a href="https://github.com/morriconeluca/skills/tree/main/docs/skills/zero-fluff-pi-sessions" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;C'è subito una distinzione importante: &lt;strong&gt;il comando di una skill non è soltanto una parola in più nel prompt&lt;/strong&gt;. L'ambiente dell'agente lo espande in un insieme di istruzioni. La skill &lt;code&gt;caveman&lt;/code&gt; contiene una specifica più lunga, che si apre così:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Respond terse like smart caveman. All technical substance stay. Only fluff die.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In sostanza: rispondi in modo telegrafico, da cavernicolo intelligente; mantieni tutta la sostanza tecnica, elimina soltanto il superfluo.&lt;/p&gt;

&lt;p&gt;La skill &lt;code&gt;zero-fluff&lt;/code&gt;, invece, era definita così:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;zero-fluff&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Maintain dry and synthesis-oriented communication.&lt;/span&gt;
&lt;span class="na"&gt;disable-model-invocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# The Rule&lt;/span&gt;

Never use words that do not serve mutual understanding.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L'ultima frase compariva anche nella skill del mio precedente articolo. Qui rimane da sola: «Non usare mai parole che non servano alla comprensione reciproca».&lt;/p&gt;

&lt;p&gt;Non dice «scrivi la risposta più breve possibile». Non parla di algoritmi, validazione degli input, file o test. Esprime un criterio: conserva ciò che serve a capirsi.&lt;/p&gt;

&lt;p&gt;Volevo vedere come questo criterio avrebbe influenzato in concreto il lavoro dell'agente.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Dante: meno testo, ma anche una diversa selezione delle informazioni
&lt;/h2&gt;

&lt;p&gt;Il primo confronto sembrava piuttosto semplice:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condizione&lt;/th&gt;
&lt;th&gt;Caratteri della risposta finale, inclusi Markdown e spazi&lt;/th&gt;
&lt;th&gt;Forma della risposta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;2167&lt;/td&gt;
&lt;td&gt;Un profilo esteso, con sezioni, sottopunti e informazioni di contesto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No-fluff&lt;/td&gt;
&lt;td&gt;872&lt;/td&gt;
&lt;td&gt;Un'introduzione e quattro punti sintetici&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caveman&lt;/td&gt;
&lt;td&gt;384&lt;/td&gt;
&lt;td&gt;Tre brevi paragrafi, senza titoli né elenchi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero-fluff&lt;/td&gt;
&lt;td&gt;927&lt;/td&gt;
&lt;td&gt;Un'introduzione e quattro punti strutturati, in prosa ordinaria&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Cosa resta e cosa scompare
&lt;/h3&gt;

&lt;p&gt;Tutte e quattro le risposte presentavano Dante come un poeta italiano di Firenze, riportavano gli anni 1265–1321, citavano la &lt;em&gt;Commedia&lt;/em&gt; e le sue tre cantiche, descrivevano l'uso del toscano al posto del latino e ricordavano l'esilio del 1302 e la morte a Ravenna. Tutte nominavano anche la &lt;em&gt;Vita Nuova&lt;/em&gt; — scritta &lt;em&gt;Vita Nova&lt;/em&gt; nella risposta caveman — e il &lt;em&gt;De Vulgari Eloquentia&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Piccola parentesi:&lt;/strong&gt; &lt;em&gt;Vita Nuova&lt;/em&gt; è il titolo nella forma tradizionale e più diffusa nella storia editoriale e scolastica italiana; &lt;em&gt;Vita Nova&lt;/em&gt; è la forma latina originaria, presente nei manoscritti più antichi e riproposta da importanti edizioni critiche.&lt;/p&gt;

&lt;p&gt;Le differenze diventano più chiare se confrontiamo i contenuti:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;La risposta &lt;strong&gt;default&lt;/strong&gt; citava le guide di Dante lungo il viaggio della &lt;em&gt;Divina Commedia&lt;/em&gt;; includeva il &lt;em&gt;Convivio&lt;/em&gt;, le Tre Corone e un resoconto più ampio della vita politica di Dante.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No-fluff&lt;/strong&gt; manteneva quattro aree principali: la &lt;em&gt;Commedia&lt;/em&gt;, l'importanza linguistica di Dante, le altre opere e la politica. Eliminava buona parte dei dettagli di contorno.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caveman&lt;/strong&gt; comprimeva di più. Non ripeteva nemmeno il nome di Dante all'inizio, affidandosi al contesto della domanda. Conservava il nucleo biografico, ma citava meno opere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-fluff&lt;/strong&gt; rimaneva vicino alla risposta strutturata di &lt;strong&gt;no-fluff&lt;/strong&gt;. Era leggermente più lungo, con dettagli più espliciti sull'appartenenza politica di Dante e sul &lt;em&gt;De Monarchia&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Nelle quattro condizioni, a cambiare non era soltanto la quantità delle parole usate, ma soprattutto la selezione delle informazioni.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ecco la versione &lt;strong&gt;caveman&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Italian poet (1265–1321) from Florence. Best known for _Divine Comedy_ (_Inferno_, _Purgatorio_, _Paradiso_).

Established Tuscan vernacular as literary standard instead of Latin. Called father of modern Italian.

Exiled from Florence in 1302 during political conflict between Black and White Guelphs. Died in Ravenna. Major works also include _Vita Nova_ and _De Vulgari Eloquentia_.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ed ecco la risposta &lt;strong&gt;zero-fluff&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gs"&gt;**Dante Alighieri**&lt;/span&gt; (c. 1265–1321) was an Italian poet, philosopher, and political thinker from Florence, best known as the author of the _Divine Comedy_ (_Divina Commedia_).

&lt;span class="gu"&gt;### Key Aspects&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Major Work:**&lt;/span&gt; The _Divine Comedy_, an epic poem describing the soul's journey through Hell (_Inferno_), Purgatory (_Purgatorio_), and Paradise (_Paradiso_).
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Linguistic Impact:**&lt;/span&gt; Often called the "Father of the Italian Language" for writing in the Tuscan vernacular rather than Latin, establishing standard literary Italian.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Other Works:**&lt;/span&gt; _Vita Nuova_ (love poetry dedicated to Beatrice), _De Monarchia_ (political treatise on universal empire), and _De Vulgari Eloquentia_ (treatise on vernacular eloquence).
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Political Life:**&lt;/span&gt; A White Guelph in Florentine politics; exiled permanently in 1302 following a rival faction's coup, spending his remaining life in various northern Italian cities until his death in Ravenna.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Su Dante, &lt;strong&gt;caveman&lt;/strong&gt; comprimeva maggiormente, mentre &lt;strong&gt;zero-fluff&lt;/strong&gt; sembrava selezionare una sintesi strutturata, non il minimo testo possibile.&lt;/p&gt;

&lt;p&gt;Ma il confronto su Fibonacci si è rivelato più interessante.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Fibonacci: cambia anche ciò che l'agente consegna
&lt;/h2&gt;

&lt;p&gt;La richiesta di coding era aperta:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Develop a fibonacci function&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Non avevo specificato un linguaggio. Non avevo chiesto un generatore della sequenza, un'interfaccia a riga di comando, una suite di test o se l'agente doveva creare file anziché rispondere con uno snippet. Avevo dato per scontato che in matematica la sequenza di Fibonacci (che prende il nome dal matematico pisano del XIII secolo Leonardo Fibonacci) è indicata come &lt;code&gt;F_n&lt;/code&gt; e definita ricorsivamente: con &lt;code&gt;F_0 = 0&lt;/code&gt;, &lt;code&gt;F_1 = 1&lt;/code&gt; e con i restanti numeri descritti come &lt;code&gt;F_n = F_{n-1} + F_{n-2}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Piccola parentesi:&lt;/strong&gt; "to develop" è un concetto ampio che probabilmente &lt;strong&gt;include semanticamente l'atto di creare file e scrivere codice&lt;/strong&gt; e che forse &lt;strong&gt;non si limita solo a questo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Questa libertà, che derivava dall'ambiguità della richiesta, è diventata parte del risultato.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condizione&lt;/th&gt;
&lt;th&gt;Cosa ha prodotto l'agente&lt;/th&gt;
&lt;th&gt;Verifiche registrate nella sessione&lt;/th&gt;
&lt;th&gt;Chiamate ai tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;Un modulo Python con funzione, generatore della sequenza, documentazione, CLI e un file di test separato&lt;/td&gt;
&lt;td&gt;Sette metodi unittest superati; eseguiti anche esempi della CLI&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No-fluff&lt;/td&gt;
&lt;td&gt;Un modulo Python con funzione e generatore, meno documentazione e un file di test separato; nessuna CLI&lt;/td&gt;
&lt;td&gt;Cinque metodi unittest superati&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caveman&lt;/td&gt;
&lt;td&gt;Una funzione e un'asserzione nella chat; nessun file Python scritto&lt;/td&gt;
&lt;td&gt;L'asserzione è stata mostrata, non eseguita&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero-fluff&lt;/td&gt;
&lt;td&gt;Un solo file Python con la funzione e un'asserzione incorporata&lt;/td&gt;
&lt;td&gt;L'agente ha avviato lo script con l'asserzione senza errori&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tutte e quattro le funzioni usavano un approccio iterativo, non ricorsivo.&lt;/p&gt;

&lt;p&gt;Cambiava, però, l'ampiezza della soluzione.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La risposta finale di coding più breve non corrispondeva alla minore attività dell'agente, ma più tool non significa automaticamente più verifica.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero-fluff&lt;/strong&gt; effettuava otto chiamate ai tool anziché una di &lt;strong&gt;caveman&lt;/strong&gt;. Tuttavia, cinque servivano a esplorare il filesystem e non erano passaggi di verifica utili.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. La funzione: non si tratta soltanto di togliere commenti
&lt;/h2&gt;

&lt;p&gt;Confrontare le risposte senza farne menzione nasconderebbe una differenza importante: &lt;strong&gt;no-fluff&lt;/strong&gt; mostrava sia la funzione sia un generatore, mentre &lt;strong&gt;zero-fluff&lt;/strong&gt; mostrava soltanto la funzione.&lt;/p&gt;

&lt;p&gt;Ho quindi confrontato la funzione in sé tra le quattro condizioni.&lt;/p&gt;

&lt;p&gt;Ecco la versione &lt;strong&gt;default&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&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;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;TypeError&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;n must be an integer, got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&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;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n must be a non-negative integer&lt;/span&gt;&lt;span class="sh"&gt;"&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;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ecco quella &lt;strong&gt;no-fluff&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&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;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return the n-th Fibonacci number (0-indexed: F(0) = 0, F(1) = 1).

    Time Complexity: O(n)
    Space Complexity: O(1)
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;TypeError&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;n must be an integer, got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&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;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n must be non-negative&lt;/span&gt;&lt;span class="sh"&gt;"&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;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;n&lt;/span&gt;

    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ed ecco la funzione &lt;strong&gt;caveman&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&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;int&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;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n must be non-negative&lt;/span&gt;&lt;span class="sh"&gt;"&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;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&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;n&lt;/span&gt;

    &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E quella &lt;strong&gt;zero-fluff&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&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;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return the nth Fibonacci number (0-indexed).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n must be a non-negative integer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La &lt;strong&gt;zero-fluff&lt;/strong&gt; non si limita a ridurre la documentazione. Elimina il controllo esplicito del tipo e gestisce i casi base attraverso il ciclo, invece di farlo con uno o due rami logici separati.&lt;/p&gt;

&lt;p&gt;Nelle quattro implementazioni cambia anche il comportamento su alcuni input:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;No-fluff&lt;/th&gt;
&lt;th&gt;Caveman&lt;/th&gt;
&lt;th&gt;Zero-fluff&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;True&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TypeError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Restituisce &lt;code&gt;True&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Restituisce &lt;code&gt;True&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Restituisce &lt;code&gt;1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TypeError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TypeError&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Restituisce &lt;code&gt;0.5&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TypeError&lt;/code&gt; da &lt;code&gt;range&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ecco altre differenze significative su cui porre attenzione:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tra &lt;strong&gt;default&lt;/strong&gt; e &lt;strong&gt;no-fluff&lt;/strong&gt; cambia l'ampiezza della soluzione, ma in entrambe le sessioni l'agente scrive file e svolge separatamente i test.&lt;/li&gt;
&lt;li&gt;tra &lt;strong&gt;caveman&lt;/strong&gt; e &lt;strong&gt;zero-fluff&lt;/strong&gt; cambia anche il significato operativo della consegna: &lt;strong&gt;mostrare una soluzione&lt;/strong&gt; contro &lt;strong&gt;produrre e invocare un solo file che includeva implementazione e una semplice autoverifica&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. La mia interpretazione: la stratificazione semantica negli LLM
&lt;/h2&gt;

&lt;p&gt;Chiamo &lt;strong&gt;stratificazione semantica negli LLM&lt;/strong&gt; l'ipotesi interpretativa con cui sto leggendo questo esperimento: per cui ogni parola aggiunta al contesto eredita una stratificazione di senso.&lt;/p&gt;

&lt;p&gt;Intendo questo: un'espressione può portare con sé più aspettative collegate.&lt;/p&gt;

&lt;p&gt;Un'istruzione può orientare criteri applicabili a più dimensioni del compito, oltre quella che il suo autore intendeva modificare. Un'indicazione rivolta a un ambito può influenzare altri ambiti.&lt;/p&gt;

&lt;p&gt;"No fluff" può suggerire meno spiegazioni, meno commenti, un esempio più piccolo o un'interpretazione più ristretta di ciò che occorre consegnare. Una regola sulla "comprensione reciproca" può diventare un criterio di selezione anche nel coding.&lt;/p&gt;

&lt;p&gt;La mia ipotesi è che i concetti all'interno degli LLM non sono compartimenti stagni (dizionari separati per "storia" e "programmazione"), ma sono proiettati su un &lt;em&gt;continuum semantico&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Questo esperimento, però, ha solo la valenza di un gioco dettato dalla curiosità.&lt;/p&gt;

&lt;p&gt;C'è poi un punto da sottolineare, anche più banale: &lt;strong&gt;un effetto può essere inatteso dal punto di vista dell'utente, senza essere estraneo a un'interpretazione plausibile dell'istruzione&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nella progettazione di skill, prompt e system prompt, questa distinzione conta più di una spiegazione sul funzionamento interno del modello:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;L'ambito che abbiamo in mente quando scriviamo una regola non corrisponde necessariamente all'ambito all'interno del quale l'agente la applica.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Discutiamone
&lt;/h2&gt;

&lt;p&gt;Ti è mai capitato che un'istruzione pensata per altro cambiasse il codice o le azioni del tuo agente? Come verificheresti che una skill rimanga nell'ambito per cui l'hai progettata?&lt;/p&gt;

&lt;p&gt;Fammi sapere cosa ne pensi nei commenti qui sotto!&lt;/p&gt;

</description>
      <category>italian</category>
      <category>ai</category>
      <category>coding</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Complete Guide to Silent Installation of GBase Database (GBase 8s)</title>
      <dc:creator>mmllllzcn</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:17:44 +0000</pubDate>
      <link>https://dev.to/mmllllzcn/complete-guide-to-silent-installation-of-gbase-database-gbase-8s-3cd7</link>
      <guid>https://dev.to/mmllllzcn/complete-guide-to-silent-installation-of-gbase-database-gbase-8s-3cd7</guid>
      <description>&lt;p&gt;Silent installation is a practical way to deploy GBase Database(GBase 8s) across multiple servers without manually navigating the installation wizard on every machine.&lt;/p&gt;

&lt;p&gt;Instead of entering installation options interactively, you define the required settings in a response file and execute the installer from the command line. This approach can reduce repetitive work, improve consistency, and provide a foundation for automated database deployment.&lt;/p&gt;

&lt;p&gt;This guide covers response file preparation, parameter configuration, silent installation, and post-installation validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Prepare the Installation Response File
&lt;/h2&gt;

&lt;p&gt;There are two common approaches to preparing a response file for silent installation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A: Record an interactive installation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run the installer in record mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./ids_install &lt;span class="nt"&gt;-r&lt;/span&gt; /opt/bundle.properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the prompts to record the installation choices supported by your installer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B: Edit the provided template&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some installation media include a &lt;code&gt;bundle.properties&lt;/code&gt; template. You can copy it to a suitable location and modify the parameters required for your deployment.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;bundle.properties /opt/bundle.properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure the source file is the correct template for your GBase Database installation package. The available parameters and accepted values may differ between installer versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Configure the Key Parameters
&lt;/h2&gt;

&lt;p&gt;Before running the silent installer, review the response file carefully.&lt;/p&gt;

&lt;p&gt;The following example illustrates several settings that may be relevant to the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;INSTALLER_UI&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;silent&lt;/span&gt;
&lt;span class="py"&gt;USER_INSTALL_DIR&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/opt/GBASE/gbase&lt;/span&gt;
&lt;span class="py"&gt;LICENSE_ACCEPTED&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;TRUE&lt;/span&gt;
&lt;span class="py"&gt;SELECTED_FEATURES&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;GBase database server,Backup and Restore&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These values are illustrative. Confirm the exact parameter names, syntax, component identifiers, and accepted values against the &lt;code&gt;bundle.properties&lt;/code&gt; template supplied with your installation media.&lt;/p&gt;

&lt;p&gt;Pay particular attention to the following items.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation directory&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;USER_INSTALL_DIR&lt;/code&gt; specifies the installation path when supported by the installer. The example uses &lt;code&gt;/opt/GBASE/gbase&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Check filesystem capacity, directory permissions, and the planned storage layout before deploying to production servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;License acceptance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LICENSE_ACCEPTED&lt;/code&gt; represents license acceptance in this example. Review the applicable license terms and explicitly accept them before setting this parameter to &lt;code&gt;TRUE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Component selection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECTED_FEATURES&lt;/code&gt; represents the selected installation components. Choose components according to the requirements of your deployment rather than assuming that every optional component is necessary.&lt;/p&gt;

&lt;p&gt;Validate component identifiers against the supplied response file. Do not assume that descriptive names copied from an installation wizard will always be accepted as parameter values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Execute the Silent Installation
&lt;/h2&gt;

&lt;p&gt;After preparing the response file, run the installer in silent mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./ids_install &lt;span class="nt"&gt;-i&lt;/span&gt; silent &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; /opt/bundle.properties &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-DLICENSE_ACCEPTED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TRUE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what the command does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-i silent&lt;/code&gt; requests silent installation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-f /opt/bundle.properties&lt;/code&gt; specifies the response file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-DLICENSE_ACCEPTED=TRUE&lt;/code&gt; supplies the license-acceptance property used by this installation command.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact syntax and property requirements should be checked against the installer documentation for your package.&lt;/p&gt;

&lt;p&gt;Run the command from the appropriate installation-media directory, with the permissions required by your installation procedure.&lt;/p&gt;

&lt;p&gt;After execution, check the installer output and logs for errors. Do not treat the command's completion alone as proof that the database is ready for application traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Complete the Database Configuration
&lt;/h2&gt;

&lt;p&gt;A successful silent installation does not necessarily mean a usable database instance has been initialized.&lt;/p&gt;

&lt;p&gt;Depending on the installation method and package, additional configuration may be required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure the environment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set the environment variables required by the installed instance. For GBase Database(GBase 8s), these may include variables identifying the installation directory, instance configuration, and connection configuration.&lt;/p&gt;

&lt;p&gt;For example, verify the values used for &lt;code&gt;ONCONFIG&lt;/code&gt; and &lt;code&gt;GBASEDBTSQLHOSTS&lt;/code&gt; where applicable. Use the instance-specific names generated or configured for your environment rather than copying values blindly from another server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review &lt;code&gt;onconfig&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check the instance configuration file and confirm that the relevant settings match the target server's resource allocation and operational requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Review &lt;code&gt;sqlhosts&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Confirm that the connection definitions use the correct server name, protocol, and service or port configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Initialize and validate the instance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Follow the initialization procedure for your installation method. Then verify the instance status and test database connectivity using the appropriate GBase Database utilities.&lt;/p&gt;

&lt;p&gt;If the instance is not running or connections fail, troubleshoot the configuration before proceeding with application deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Validate Before Scaling Out
&lt;/h2&gt;

&lt;p&gt;The main advantage of silent installation is repeatability, but repeatability is useful only when the process has been tested.&lt;/p&gt;

&lt;p&gt;Before deploying GBase Database across multiple machines, validate the complete procedure on a single representative server.&lt;/p&gt;

&lt;p&gt;Use this checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] The installer completed without errors.&lt;/li&gt;
&lt;li&gt;[ ] The selected components match the deployment requirements.&lt;/li&gt;
&lt;li&gt;[ ] Installation paths and directory ownership are correct.&lt;/li&gt;
&lt;li&gt;[ ] Required environment variables and configuration files are in place.&lt;/li&gt;
&lt;li&gt;[ ] The database instance is initialized and running.&lt;/li&gt;
&lt;li&gt;[ ] Client connections succeed.&lt;/li&gt;
&lt;li&gt;[ ] Logs have been reviewed for installation or startup issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once these checks pass, reuse the validated response file and deployment procedure for additional servers. Account for legitimate differences in host-specific settings instead of assuming that every configuration value must be identical.&lt;/p&gt;

&lt;p&gt;For production environments, also maintain version-controlled installation scripts, appropriate access controls, deployment logs, and a documented recovery procedure.&lt;/p&gt;

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

&lt;p&gt;Silent installation turns GBase Database(GBase 8s) deployment into a repeatable, script-driven process.&lt;/p&gt;

&lt;p&gt;The workflow is straightforward: prepare the response file, validate its parameters, execute the installer, complete instance configuration, and test the result before scaling out.&lt;/p&gt;

&lt;p&gt;The critical distinction is between installing database software and delivering a working database service. A reliable deployment process must cover both.&lt;/p&gt;

&lt;p&gt;For teams managing multiple GBase Database servers, a tested response file combined with post-installation validation can reduce manual effort and make database deployment more consistent across environments.&lt;/p&gt;

</description>
      <category>gbase</category>
      <category>database</category>
      <category>数据库</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>A Cost-Aware Model Router for Claude in About 80 Lines of Python</title>
      <dc:creator>Darius Tran</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:17:16 +0000</pubDate>
      <link>https://dev.to/dariustran/a-cost-aware-model-router-for-claude-in-about-80-lines-of-python-541c</link>
      <guid>https://dev.to/dariustran/a-cost-aware-model-router-for-claude-in-about-80-lines-of-python-541c</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6n3ro2ta9g51c5lkplqh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6n3ro2ta9g51c5lkplqh.jpg" alt="A Cost-Aware Model Router for Claude in About 80 Lines of Python" width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
The fastest way to cut an LLM bill without hurting quality is to stop sending every request to your most capable model. Most traffic in a typical agent workflow is classification, extraction, or short replies. A smaller, faster model handles those fine. The bigger model should be reserved for the steps where it measurably changes the outcome.&lt;/p&gt;

&lt;p&gt;This post walks through the simple router I use with Claude models. It's not a framework. It's a pattern you can adapt in an afternoon.&lt;/p&gt;
&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Every request gets a cheap first pass that decides how hard it is. Easy requests go to a fast model. Hard ones go to a stronger model. If the fast model's answer fails a check, the request is retried on the stronger one.&lt;/p&gt;

&lt;p&gt;Three ingredients:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A difficulty signal&lt;/strong&gt;: rules, a small classifier, or a short call to the fast model itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routing rules&lt;/strong&gt; kept in config, not hard-coded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An escalation path&lt;/strong&gt; when the cheaper answer isn't good enough.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  The router
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;ROUTES&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;fast&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;model&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;claude-sonnet-5&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;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strong&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;model&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;claude-opus-5-5&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;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;HARD_TASKS&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;multi_step_plan&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;contract_review&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;root_cause_analysis&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;task_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;needs_tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pick_route&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;Request&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="k"&gt;if&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;task_type&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;HARD_TASKS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strong&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&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;text&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;20_000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strong&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&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;needs_tools&lt;/span&gt; &lt;span class="ow"&gt;and&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;task_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;multi_step_plan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strong&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&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;system&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;user&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;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ROUTES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&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;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&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;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;user&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;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;}],&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;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&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;The rules here are deliberately boring. Task type and input size go a long way before you need anything clever. Check the current model names and pricing in Anthropic's documentation before copying this, since both change over time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Escalation when the cheap answer isn't good enough
&lt;/h2&gt;

&lt;p&gt;Routing up front catches the obvious cases. Escalation catches the rest:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_text&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;schema_keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&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;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;schema_keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issubset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;route_and_run&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;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system&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;schema_keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pick_route&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;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system&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;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schema_keys&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strong&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system&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;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
        &lt;span class="nf"&gt;log_escalation&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;task_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;validation_failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;log_usage&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;task_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&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;text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validation can be anything cheap and deterministic: valid JSON, required fields present, a confidence field above a threshold, or a citation check for retrieval answers. Avoid using another LLM call as the validator for every request, or you lose most of the savings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring whether it works
&lt;/h2&gt;

&lt;p&gt;A router without measurement is a guess. I log four things per request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Task type&lt;/li&gt;
&lt;li&gt;Model used&lt;/li&gt;
&lt;li&gt;Input and output tokens&lt;/li&gt;
&lt;li&gt;Whether it escalated, and why&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From that, two numbers matter most:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Escalation rate per task type.&lt;/strong&gt; If a task escalates more than about a third of the time, it probably belongs on the strong route from the start. Paying for two calls is worse than paying for one larger one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quality by route.&lt;/strong&gt; Sample outputs from both routes each week and score them against the same rubric. The goal is equal quality at lower cost, not lower cost at any quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where routing goes wrong
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Routing on the wrong signal.&lt;/strong&gt; Text length is a weak proxy for difficulty. A short question about a complex contract can be hard, and a long but simple form can be easy. Task type is usually the better signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ignoring latency.&lt;/strong&gt; The fast model is often fast as well as cheap. For user-facing steps, that matters as much as cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting tool use.&lt;/strong&gt; Multi-step tool use with dependent calls is where larger models tend to be more reliable. I keep planning steps on the strong route even when the text is short.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hard-coding models.&lt;/strong&gt; Models change often. Keep routes in config so switching a model is a deploy-free change, and rerun your eval set when you do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a learned difficulty signal later
&lt;/h2&gt;

&lt;p&gt;Rules get you surprisingly far, but eventually you'll want a better signal than task type. The cheapest upgrade I've found is to let your own logs train it.&lt;/p&gt;

&lt;p&gt;After a few weeks of routing, you have labelled data for free: requests that the fast model handled and passed validation, and requests that escalated. A small classifier on simple features can learn which new requests are likely to escalate and send them straight to the strong route.&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.linear_model&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LogisticRegression&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;features&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;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&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="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&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;int&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;needs_tools&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;int&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;task_type&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;HARD_TASKS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;count_entities&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;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# IDs, amounts, dates mentioned
&lt;/span&gt;    &lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;X&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;logged_requests&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;escalated&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;logged_requests&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;clf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LogisticRegression&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pick_route_learned&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;Request&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="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;predict_proba&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;features&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="mi"&gt;0&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="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strong&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.4&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;fast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the threshold in config and tune it against your eval set. A lower threshold sends more traffic to the strong model, raising cost and lowering escalations. The right point depends on how much you value latency and how expensive a wasted fast call is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost guardrails beyond routing
&lt;/h2&gt;

&lt;p&gt;Routing reduces average cost. It doesn't stop a single runaway task. I add two simple guards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A token budget per task&lt;/strong&gt;, enforced in the agent loop. When a task crosses it, the agent summarizes its progress and hands off instead of continuing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A daily budget per tenant or team&lt;/strong&gt;, with an alert at 80 percent. This catches a new integration that suddenly sends ten times the expected traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How much does it save?
&lt;/h2&gt;

&lt;p&gt;It depends entirely on your traffic mix. If most of your requests are simple, routing makes a large difference. If most are complex reasoning, it helps less. The only reliable way to know is to log a week of real traffic by task type, then simulate the routing rules against it before turning them on.&lt;/p&gt;

&lt;p&gt;For a deeper comparison of the two models, including where the larger one tends to justify its price, I wrote up &lt;a href="https://aihive.global/blog/claude-sonnet-vs-opus/" rel="noopener noreferrer"&gt;&lt;strong&gt;when Sonnet is enough and when Opus earns its cost&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I work on enterprise agent deployments at &lt;a href="https://aihive.global/" rel="noopener noreferrer"&gt;&lt;strong&gt;AI Hive&lt;/strong&gt;&lt;/a&gt;, where model routing is one of the first things we set up for cost control. How do you decide which requests get your most expensive model?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>python</category>
      <category>claude</category>
    </item>
    <item>
      <title>We locked 25,396 behaviors before our agent refactors its own code, bugs included</title>
      <dc:creator>Rulestack</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:17:00 +0000</pubDate>
      <link>https://dev.to/rulestack/we-locked-25396-behaviors-before-our-agent-refactors-its-own-code-bugs-included-57hc</link>
      <guid>https://dev.to/rulestack/we-locked-25396-behaviors-before-our-agent-refactors-its-own-code-bugs-included-57hc</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Before letting our Claude Code agent restructure its own codebase, we had it write 25,396 tests in 769 files that pin what the code does today, bugs included, each one running in a sandbox with no writes into the repo, no network and no secrets. So far six commits have had to change a pinned expectation and say why, three bugs came out of the work of writing the cases, and one full run took 8 minutes 44 seconds on a heavily loaded 10-core machine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What we were protecting
&lt;/h2&gt;

&lt;p&gt;Our agent maintains its own TypeScript codebase: the scripts behind its daily jobs, about 230 command-line tools under &lt;code&gt;src/commands&lt;/code&gt;, the checks that run before every commit, and the JSONL ledgers it reads to decide what to do next. On 2026-10-08 the owner asked for tests that would hold every current behavior in place through a very large refactor, a move toward a domain-driven layout. (Dates in this post are our commit timestamps, which are in UTC+9.)&lt;/p&gt;

&lt;p&gt;That request rules out the usual kind of test. A unit test asks whether the code is right. A behavior lock, often called a characterization test, asks something narrower: is the code doing exactly what it did yesterday? The expected values come from running today's code, not from what the design says it should do. When the agent's case list and the code disagreed, the test pinned the code, and the disagreement went to the owner as a separate question.&lt;/p&gt;

&lt;p&gt;One of those disagreements is still pinned on purpose. Case [024] belongs to a pre-send check on reply drafts, which stops a reply from quoting a number that appears in one of our article titles but in no article body. Given a draft that says "Claude Code 2.1", the check treats the lone "2.1" as a measured number rather than part of a version, and flags it. The comment above the test says it stays that way until the owner decides, so that the refactor can't quietly change it either way.&lt;/p&gt;

&lt;p&gt;The rule for a failing lock test is one line in our coding rules: if the change wasn't intended, fix the code; if it was, change the expectation and write the reason in the commit message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shrinking the target first
&lt;/h2&gt;

&lt;p&gt;The first commits weren't tests. On 2026-10-08, before any lock test existed, the agent deleted exported functions, types and files that nothing in production called, along with two unused dependencies. That removed 1,358 lines from &lt;code&gt;src/&lt;/code&gt; that would otherwise have needed protecting.&lt;/p&gt;

&lt;p&gt;It then exported &lt;code&gt;main&lt;/code&gt; from all 230 CLI files so a test could call them directly. While doing that it found 23 CLIs that ran &lt;code&gt;main&lt;/code&gt; as a side effect of being imported, and changed them to start only when launched directly, like the other 207. It also moved 29 commit-gate checks that lived inside test files into &lt;code&gt;src/quality/gates&lt;/code&gt;, so they could be fed made-up input instead of only the real ledgers, and moved the remaining ones four days later.&lt;/p&gt;

&lt;p&gt;The same day, it measured coverage across all of &lt;code&gt;src/&lt;/code&gt;: 64.6% of statements and 59.2% of branches.&lt;/p&gt;

&lt;h2&gt;
  
  
  A case list, then one test per case
&lt;/h2&gt;

&lt;p&gt;The agent wrote the case list as a separate document before writing any test. Each case has an id, and each &lt;code&gt;it&lt;/code&gt; title in &lt;code&gt;test/behavior-lock/&lt;/code&gt; starts with that id in brackets, followed by the behavior in one sentence. Table-driven tests reuse one id for several rows, and the ids restart in each file. In the run we did for this post, 25,310 of the 25,396 tests started with an id, across 23,590 distinct file-and-id pairs. Of the 86 without one, 26 are the harness's own self-tests and the other 60 were added afterwards, most of them to reach code that no listed case touched.&lt;/p&gt;

&lt;p&gt;The 769 files mirror the source tree: 257 for the CLIs, 156 for marketing logic, 68 for commit gates, 68 for repository files (GitHub Actions workflows, shell scripts, git hooks and config), 55 each for analytics and ops, 43 for reports, 23 for API clients, 22 for products, 17 for quality checks, 4 for media samples, and the harness. Together they come to 504,824 lines of test code. The source they protect is 162,385 lines.&lt;/p&gt;

&lt;p&gt;By the time the last file landed, coverage across &lt;code&gt;src/&lt;/code&gt; with all tests together was 99.1% of statements and 98.3% of branches. The owner then asked that anything short of 100% come with a reason the owner approves, so the agent replaced the percentage floor with a ledger: 235 places in the code that no test reaches, written up in 222 rows, each with a reason. As of 2026-10-13, none of the 222 rows had been approved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sandbox
&lt;/h2&gt;

&lt;p&gt;A test that writes to the real ledgers, calls a real API or reads a real token is worse than no test before a refactor: it can fail for reasons that have nothing to do with the code, and it can post something. So every file under &lt;code&gt;test/behavior-lock/&lt;/code&gt; gets &lt;code&gt;helpers/setup.ts&lt;/code&gt;, which Jest loads through &lt;code&gt;setupFilesAfterEnv&lt;/code&gt;. The Jest docs describe those modules as code that runs "before each test file is executed". Our setup checks the test file's path first and leaves every other test file alone; 404 of our 1,173 test files sit outside the lock.&lt;/p&gt;

&lt;p&gt;For a lock file, the setup creates a temporary directory, moves the working directory into it, points both &lt;code&gt;HOME&lt;/code&gt; and &lt;code&gt;os.homedir()&lt;/code&gt; at a &lt;code&gt;home/&lt;/code&gt; folder inside it, and empties the whole directory before every test. Code that reads a ledger like &lt;code&gt;state/feedback.jsonl&lt;/code&gt; by relative path sees only what the test put there. File writes inside the repository or anywhere outside the OS temp directory are blocked, and so is starting a child process whose working directory is inside the repo. &lt;code&gt;fetch&lt;/code&gt;, &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;https&lt;/code&gt; are blocked except for localhost. Environment variables whose names look like secrets (TOKEN, PASSWORD, COOKIE and so on) are deleted, along with the ones that change depending on where the tests run, such as &lt;code&gt;CI&lt;/code&gt;, &lt;code&gt;GITHUB_*&lt;/code&gt; and &lt;code&gt;CLAUDE_*&lt;/code&gt;. &lt;code&gt;TZ&lt;/code&gt; is fixed to Asia/Tokyo.&lt;/p&gt;

&lt;p&gt;Throwing an error isn't enough on its own, because the code under test may catch it and carry on. So every guard also records what it stopped, and the test fails at the end if anything was recorded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useRealTimers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;violations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;isolationViolations&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;isolationViolations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="c1"&gt;// message translated&lt;/span&gt;
            &lt;span class="s2"&gt;`behavior-lock: an operation broke the isolation rules\n- &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;violations&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="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;- &lt;/span&gt;&lt;span class="dl"&gt;'&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="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&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 CLIs run through &lt;code&gt;helpers/run-command.ts&lt;/code&gt;. It writes &lt;code&gt;input.json&lt;/code&gt; into the sandbox, sets &lt;code&gt;process.argv&lt;/code&gt; as if &lt;code&gt;pnpm&lt;/code&gt; had launched the command, collects stdout, stderr and console output, turns &lt;code&gt;process.exit&lt;/code&gt; into a recorded exit code, and hands &lt;code&gt;main&lt;/code&gt; to the same &lt;code&gt;runMain&lt;/code&gt; wrapper production uses, so a failure goes through the real error logging and exit code 1. The commands run inside the test process rather than as child processes, because spawning a process for every CLI test would be slow and wouldn't count toward coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sandbox leaked while we built it
&lt;/h2&gt;

&lt;p&gt;The setup file got its current shape one hole at a time. Six of them needed fixing before the lock held.&lt;/p&gt;

&lt;p&gt;The first is recorded in the comment of the very first version, from 2026-10-08. A test that changed only &lt;code&gt;HOME&lt;/code&gt; rewrote the real record our request-pacing gate keeps under &lt;code&gt;~/.cache&lt;/code&gt;, because &lt;code&gt;os.homedir()&lt;/code&gt; didn't follow &lt;code&gt;HOME&lt;/code&gt;. From then on both point into the sandbox.&lt;/p&gt;

&lt;p&gt;On 2026-10-12 four more turned up in a single day. We found that inside a Jest test, &lt;code&gt;process.env&lt;/code&gt; is a copy. Child processes started without an explicit &lt;code&gt;env&lt;/code&gt; got the machine's real environment instead, so the home redirect and the secret stripping never reached the shell scripts and &lt;code&gt;git&lt;/code&gt; calls under test. The setup now passes the test's environment to every child process call that doesn't bring its own. Seven minutes later came the next fix: variables like &lt;code&gt;GITHUB_ACTIONS&lt;/code&gt; and &lt;code&gt;CLAUDE_CODE_SESSION_ID&lt;/code&gt; change which code path runs, and a session cookie and some signup-email values had slipped past the secret pattern; both are now stripped. That evening a pre-commit review pointed out that blocking &lt;code&gt;fetch&lt;/code&gt; only covers the test process: &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;git&lt;/code&gt; and &lt;code&gt;pnpm&lt;/code&gt; started as child processes could still reach the internet. Child processes now get every proxy variable, in upper and lower case (&lt;code&gt;http_proxy&lt;/code&gt;, &lt;code&gt;HTTPS_PROXY&lt;/code&gt;, &lt;code&gt;ALL_PROXY&lt;/code&gt; and the rest), pointed at &lt;code&gt;127.0.0.1:9&lt;/code&gt;, a local port nothing answers on, with localhost let through by &lt;code&gt;NO_PROXY&lt;/code&gt;. They also get &lt;code&gt;GIT_SSH_COMMAND=false&lt;/code&gt;, so git has no working command to open an SSH connection with.&lt;/p&gt;

&lt;p&gt;The fourth, just before midnight, ran the other way. After a test file finishes, Jest restores every spy a test left in place to the value it had when the spy was created (in jest-runtime 30.4.2, the runtime's &lt;code&gt;teardown()&lt;/code&gt; calls &lt;code&gt;restoreAllMocks()&lt;/code&gt;), and for the functions we guard, that value was our guard. &lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;child_process&lt;/code&gt;, &lt;code&gt;os&lt;/code&gt; and &lt;code&gt;http&lt;/code&gt; are shared with the next test file in the same worker, so older tests outside the lock started tripping our guard against child processes inside the repository, but only on a full run, and only in some orders. Each guard is now wrapped so that it steps aside once its file's isolation has ended:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;delegateToOriginalAfterIsolation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;guard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;isActive&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="nl"&gt;guard&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ShapeAgnosticFunction&lt;/span&gt;
    &lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ShapeAgnosticFunction&lt;/span&gt;
    &lt;span class="nx"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nx"&gt;ShapeAgnosticFunction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;guardWhileIsolated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;functionArguments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&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;return&lt;/span&gt; &lt;span class="nb"&gt;Reflect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;guard&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;functionArguments&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last one showed up in CI. The run for the push that landed the lock failed three lock tests that had passed locally. CI runs in UTC, and setting &lt;code&gt;TZ&lt;/code&gt; inside the setup file hadn't changed how &lt;code&gt;Date&lt;/code&gt; read times, for the same reason as before: the test's &lt;code&gt;process.env&lt;/code&gt; is a copy. &lt;code&gt;TZ&lt;/code&gt; moved into &lt;code&gt;jest.config.js&lt;/code&gt;, which the worker processes inherit. The other two failures came from Node versions: whether an aborted request's &lt;code&gt;DOMException&lt;/code&gt; counted as an &lt;code&gt;Error&lt;/code&gt; inside Jest differed between Node 22.17 on CI and 22.22 locally, which changed whether the message started with &lt;code&gt;TimeoutError:&lt;/code&gt; or &lt;code&gt;AbortError:&lt;/code&gt;. Those two tests now accept both forms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the lock has caught
&lt;/h2&gt;

&lt;p&gt;Six commits so far have changed a pinned expectation because the code changed.&lt;/p&gt;

&lt;p&gt;The one we would point to first is lock test [050]. It covers the command that sends replies to direct messages, and its title described what the code did in one sentence:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F171qlly3p4skh9yidpjm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F171qlly3p4skh9yidpjm.png" alt="A lock test title, translated: " width="800" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every reply to a direct message is supposed to have the owner's approval. A message the agent had once marked as "won't reply" was left out of all three pre-send checks, so a reply written to it later would go out unapproved. The commit that fixed it counted 5 rows in the live DM ledger in exactly that state. The owner ruled that those replies need approval too, and the fix rewrote [050] to expect the approval gate to stop the send, then added &lt;a href="https://dev.toapproved,%20so%20it%20sends"&gt;059&lt;/a&gt; and &lt;a href="https://dev.tothe%20message%20is%20product%20feedback%20and%20the%20owner%20hasn't%20given%20direction%20yet,%20so%20it%20stops"&gt;060&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The same night, two more bugs found while writing the tests were fixed: the weekly report crashed with a &lt;code&gt;TypeError&lt;/code&gt; when the year-end goals had no dev.to follower metric ([081]), and a generated label was missing a space ([034]). A separate session also fixed three bugs that night, and the lock flagged them: 9 existing assertions in the tests for the command that restarts missed scheduled posts now expect a &lt;code&gt;gh api&lt;/code&gt; call that reads the workflow's state before anything else, and case [045] in the engagement planner went from "counts likes, reposts, quotes and replies as 0" to "returns the reason and records nothing". That second change was one more bug the lock had been holding in place: counting a week it couldn't fetch as zero could record a false "we're on track".&lt;/p&gt;

&lt;p&gt;The fourth commit is the one that worries us about the refactor ahead. The agent moved the "was this file launched directly?" check of all 230 CLIs into one helper. Nothing a user could see changed, and expectations in 69 lock files still had to change, because each of those files has a case asserting which internal function the CLI hands itself to when it starts. That is wiring, not behavior, and the lock can't tell the difference.&lt;/p&gt;

&lt;p&gt;The last two were small and deliberate. A change that stops goal tracking from nagging about metrics it can't measure while one of the agent's outside accounts is suspended added a field to an input object; the lock update was 2 lines. Then on 2026-10-13 the agent widened a hook that stops bare &lt;code&gt;open&lt;/code&gt; calls on URLs, after a loop of bare &lt;code&gt;open&lt;/code&gt; calls sent 9 links to the wrong Chrome profile for the third time. Six cases moved from "allowed" to "blocked": &lt;code&gt;open -a Safari https://x&lt;/code&gt;, &lt;code&gt;open --new https://x&lt;/code&gt;, &lt;code&gt;open x.HTML&lt;/code&gt;, &lt;code&gt;open report.HTML&lt;/code&gt;, &lt;code&gt;open "/tmp/a b.html"&lt;/code&gt; and &lt;code&gt;OPEN https://x&lt;/code&gt;. Each moved case carries a comment saying it was moved on purpose on that date, and why.&lt;/p&gt;

&lt;p&gt;Two more commits changed lock tests without the code under test changing: the CI fix above, and one where a test [007] demanded at least 6 lock re-acquisitions within 1.5 seconds and didn't get there during a full parallel run. It now waits 5 seconds and asks for at least 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;We ran the whole lock once for this post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7d5za2u9h2us3wo4mqrp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7d5za2u9h2us3wo4mqrp.png" alt="Terminal: pnpm exec jest test/behavior-lock reports 769 of 769 test suites passed, 25396 of 25396 tests passed, time 523.505 s" width="799" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is 8 minutes 44 seconds of Jest time on a 10-core machine that other sessions were keeping busy: the one-minute load average read 97 when the run started and 92 when it ended, and the five-minute average reached 136. Treat it as an upper bound rather than a benchmark; we don't have a quiet-machine number. 510 of the 769 files finished in under 5 seconds and 16 took more than 30. The slowest file took 133 seconds. It locks the commit gate that checks each scheduled GitHub Actions workflow does exactly one task, and it simulates eight days of schedules minute by minute.&lt;/p&gt;

&lt;p&gt;The other costs are the ones above: half a million lines of test code that someone has to read when an expectation changes, a sandbox that needed six fixes before it held, and tests that pin wiring as firmly as behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we don't know yet
&lt;/h2&gt;

&lt;p&gt;The refactor itself hasn't started. Since the lock landed, two commits have touched &lt;code&gt;src/&lt;/code&gt;, both small fixes for while an outside account is suspended, and one of them is the goal-tracking change above. The question we can't answer yet is how many of the 25,396 tests assert wiring rather than behavior. The 69-file commit suggests the answer isn't small: a change that nobody outside the code would notice still moved 69 files. Under our rule, every changed expectation needs a reason in the commit message, and a domain-driven restructuring moves a lot of wiring. We may have to split the wiring cases out before the refactor, so that a failing lock test means what it is supposed to mean.&lt;/p&gt;

&lt;p&gt;If you have locked a codebase's behavior before a big refactor, we'd like to know how it went. Did your expected values come from the code or from the spec? How did you stop the tests from pinning which function calls which, and was it worth separating those cases from the behavioral ones? And how did you keep tests from touching anything real? Tell us in the comments below; we'll answer each one there.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>testing</category>
      <category>refactoring</category>
    </item>
    <item>
      <title>Find broken links on any website with Python, grouped by the page you need to fix (2026)</title>
      <dc:creator>Datawright Labs</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:16:10 +0000</pubDate>
      <link>https://dev.to/datawrightlabs/find-broken-links-on-any-website-with-python-grouped-by-the-page-you-need-to-fix-2026-5adp</link>
      <guid>https://dev.to/datawrightlabs/find-broken-links-on-any-website-with-python-grouped-by-the-page-you-need-to-fix-2026-5adp</guid>
      <description>&lt;p&gt;A broken-link report is only useful if it tells you where to go to fix things. "This URL returns 404" is half the answer. "These three pages link to it" is the part you can act on. Most quick scripts check one page at a time and lose that mapping, or they check the same footer link 50 times.&lt;/p&gt;

&lt;p&gt;This post builds a small crawler that does it properly: crawl the site, collect every outgoing URL once, check each one once, and write a CSV that maps every broken URL back to the pages that contain it. Then it looks at a real run, because the interesting part of link checking is the false positives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The approach
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Breadth-first crawl of same-site pages.&lt;/strong&gt; Start at the homepage, follow &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt; links that stay on the same host, stop at a page limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collect every outgoing URL with its source pages.&lt;/strong&gt; Links, images, scripts and stylesheets all count. A missing image or script is as broken as a dead link. Strip &lt;code&gt;#fragments&lt;/code&gt; so &lt;code&gt;/docs#install&lt;/code&gt; and &lt;code&gt;/docs#usage&lt;/code&gt; are one check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check each unique URL once, in parallel.&lt;/strong&gt; Use &lt;code&gt;GET&lt;/code&gt; with &lt;code&gt;stream=True&lt;/code&gt; instead of &lt;code&gt;HEAD&lt;/code&gt;. Plenty of servers answer &lt;code&gt;HEAD&lt;/code&gt; with 405 or 404 even when the page is fine, and some just hang on it. A streamed &lt;code&gt;GET&lt;/code&gt; reads only the headers and closes the connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry once&lt;/strong&gt; before calling something broken. Transient timeouts are common.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The script
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Usage: python3 broken_links.py https://example.com [max_pages]
# Crawls same-site pages (BFS), checks every link and image once, writes broken.csv grouped by source page.
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;concurrent.futures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ThreadPoolExecutor&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urljoin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urldefrag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&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="n"&gt;max_pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&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;2&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0 (compatible; link-check-tutorial/1.0)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# GET with stream=True reads headers only; many servers answer HEAD wrongly (405/404) or hang.
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&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;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allow_redirects&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&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;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;

&lt;span class="n"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# link -&amp;gt; pages that contain it
&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seen_pages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&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;i&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;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&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;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content-type&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="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;html.parser&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;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&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;href&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;img&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;src&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;link&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;href&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;script&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;src&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;el&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;el&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;attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mailto:&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;tel:&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;javascript:&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;data:&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;#&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;urldefrag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;urljoin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&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;tag&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen_pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;seen_pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;queue&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="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;links&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;links&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="n"&gt;broken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;st&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;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;broken.csv&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;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerow&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source_page&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;broken_url&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;status&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;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;broken&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;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerow&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&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;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; pages crawled, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;links&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; unique URLs checked, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;broken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; broken in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; s -&amp;gt; broken.csv&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;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;broken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&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="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&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="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;16&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;u&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  (on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; page(s))&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;Install the two dependencies with &lt;code&gt;pip install requests beautifulsoup4&lt;/code&gt; and run &lt;code&gt;python3 broken_links.py https://your-site.com 200&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results on crawlee.dev
&lt;/h2&gt;

&lt;p&gt;I ran &lt;code&gt;python3 broken_links.py https://crawlee.dev 50&lt;/code&gt; on October 20, 2026:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;50 pages crawled, 3,008 unique URLs checked in 245 seconds&lt;/strong&gt;, 10 checks in parallel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;35 URLs flagged.&lt;/strong&gt; That number is misleading, which is the main lesson of this post.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is what the 35 actually were:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Really broken?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;403&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;No. Stack Overflow, npm, Crunchbase, Upwork, OpenAI and others block scripted requests. The Stack Overflow tag link alone appears on 37 pages.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;999&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;No. LinkedIn's "go away, bot" status.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connect timeout&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;No. All were plain &lt;code&gt;http://&lt;/code&gt; links, and my test machine's network blocks outbound port 80. Worth knowing if you run this from a locked-down server or CI runner.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;7 yes, 1 no (see below).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSL error&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Yes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So 11 of the 35 were genuinely broken, and every one of them is a real typo or a bad link:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;https://www.crawlee.dev/python/docs/examples/playwright-crawler-with-fingeprint-generator&lt;/code&gt; on a blog post, with "fingeprint" missing an "r".&lt;/li&gt;
&lt;li&gt;Three links like &lt;code&gt;https://github.com//github.com/apify/crawlee/pull/1890/...&lt;/code&gt;, where a full URL got pasted into a field that already prefixed &lt;code&gt;https://github.com/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;https://www.en.wikipedia.org/wiki/Robots.txt&lt;/code&gt; and &lt;code&gt;https://www.developer.mozilla.org/...&lt;/code&gt;: an extra &lt;code&gt;www.&lt;/code&gt; in front of hosts that don't have one, so the TLS certificate doesn't match.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;github.com/renovate%5Bbot%5D&lt;/code&gt; profile link (bot accounts have no profile page) and two SwaggerHub API docs that no longer exist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one 404 that wasn't broken was &lt;code&gt;https://5JC94MPMLY-dsn.algolia.net&lt;/code&gt;, a &lt;code&gt;&amp;lt;link rel="preconnect"&amp;gt;&lt;/code&gt; to the search provider. Its root path 404s, but nobody ever navigates there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things that will bite you
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Treat 401, 403, 429 and 999 as "blocked", not "broken".&lt;/strong&gt; Report them in a separate column, or re-check them from a real browser. If you count them as broken, your report is mostly noise and people stop reading it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skip &lt;code&gt;preconnect&lt;/code&gt; and &lt;code&gt;dns-prefetch&lt;/code&gt; links&lt;/strong&gt;, or at least don't treat them as pages. They point at hosts, not documents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check from a normal network.&lt;/strong&gt; Corporate proxies and CI runners often block plain HTTP or whole domains, which shows up as timeouts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be polite on your own site.&lt;/strong&gt; This script checks external URLs 10 at a time but has no per-host limit. On a big site most links point at your own host, so add a per-host concurrency cap and respect &lt;code&gt;robots.txt&lt;/code&gt; before you crawl thousands of pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch the redirect chains.&lt;/strong&gt; &lt;code&gt;allow_redirects=True&lt;/code&gt; hides a 301 to a 404. The final status is what matters for users, but &lt;code&gt;r.history&lt;/code&gt; tells you which links are worth updating to skip the redirect.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hosted version
&lt;/h2&gt;

&lt;p&gt;Disclosure: I'm with Datawright Labs, and we packaged this as a hosted tool on Apify: &lt;a href="https://apify.com/datawright-labs/broken-link-checker" rel="noopener noreferrer"&gt;Broken Link Checker&lt;/a&gt;. It adds &lt;code&gt;robots.txt&lt;/code&gt; handling, a per-host concurrency limit, a spending cap per run, and source-page grouping for every broken link, image and script. It costs $0.50 per 1,000 URLs checked, so Apify's free monthly credit covers small and medium sites, and you can run it on a schedule or call it by API. The script above is the whole idea, though, so use whichever fits.&lt;/p&gt;

</description>
      <category>python</category>
      <category>seo</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
