<?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: ilien</title>
    <description>The latest articles on DEV Community by ilien (@ilien).</description>
    <link>https://dev.to/ilien</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4137952%2Ff721fd95-02d9-472e-9531-4e70cc440bc1.jpg</url>
      <title>DEV Community: ilien</title>
      <link>https://dev.to/ilien</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ilien"/>
    <language>en</language>
    <item>
      <title>"Write Like a Human" Wasn't Enough, So I Built a Skill That Measures AI Prose</title>
      <dc:creator>ilien</dc:creator>
      <pubDate>Wed, 23 Sep 2026 17:48:51 +0000</pubDate>
      <link>https://dev.to/ilien/write-like-a-human-wasnt-enough-so-i-built-a-skill-that-measures-ai-prose-4gcj</link>
      <guid>https://dev.to/ilien/write-like-a-human-wasnt-enough-so-i-built-a-skill-that-measures-ai-prose-4gcj</guid>
      <description>&lt;p&gt;Most of the documentation I read at work these days was written with AI. READMEs, internal documentation and notes shared within the team. I don't have a problem with that. Nobody is going to write the documentation for every project by hand, because that would throw away most of the time AI saves you. The problem is that I have to read those texts again and again, and they're tiring. They read bland and impersonal, they pile up filler before getting to the point, and sometimes the structure is so unnatural that they end up confusing. You read them to stay informed and to not leave everything in the hands of the AI, and it gets heavy.&lt;/p&gt;

&lt;p&gt;So I built Quirón, a skill for Claude Code (and other agents) that edits that kind of text and then measures the result. It wasn't my first try. It's the result of several experimental skills that never quite did what I wanted, plus trying other skills very close to it. This post is about what I learned on the way, including one result that didn't go the way I hoped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Telling the model to "write like a human" makes it overshoot
&lt;/h2&gt;

&lt;p&gt;The first thing everyone tries is adding "write like a human" to the prompt. It doesn't work, and the reason is interesting.&lt;/p&gt;

&lt;p&gt;A 2026 working paper (TextPulse Research, not peer reviewed) gave four flagship models a detailed brief on what separates human prose from assistant prose: uneven sentence length, plain words, no lists of three, no connective openers. The models followed it. Then they kept going. Sentence-length variation went to 0.58 where the humans were at 0.40. Claude Opus 5 overshot the most of the four. The one property the brief didn't name, vocabulary range, didn't move at all.&lt;/p&gt;

&lt;p&gt;So you trade one tell for another: prose choppier and plainer than any person writes. You can't feel where the ceiling is while you're writing. You have to measure it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring against real dev.to posts
&lt;/h2&gt;

&lt;p&gt;That's the core of the skill. It has a list of 33 patterns to remove (the "it's not X, it's Y" contrasts, lists of three, a summary section at the end, headings everywhere, the one-line closers that restate the paragraph), and a script that measures 23 features of the text.&lt;/p&gt;

&lt;p&gt;The human baseline is a bit of a love letter to this site: 167 dev.to posts by 21 authors, all published before 2022, so before ChatGPT. For each feature the script takes the 10th to 90th percentile range of those posts. Then it tells you whether your text sits inside it, on the AI side, or past the human side. Every fourth human post was held out and never used to pick anything.&lt;/p&gt;

&lt;p&gt;This is what it says about an assistant-written Stripe tutorial from the repository's evaluation set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feature                   this   human band      verdict
long words (7+) /1k     353.83   159.68 - 250.46  above band, AI side
nominalizations /1k      55.21     7.29 - 32.65   above band, AI side
em dashes /1k             3.76     0.00 - 2.74    above band, AI side
lists of three /1k       11.29     0.00 - 5.51    above band, AI side
plain words /1k          33.88    43.19 - 95.24   below band, AI side
...
13/23 features inside the human band (p10-p90 of 167 human texts)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The rewrite of that same post, built from the author's own notes, scores 23 of 23.&lt;/p&gt;

&lt;p&gt;A few things surprised me when the numbers came in. &lt;em&gt;Delve&lt;/em&gt; barely matters anymore: it showed up in 1% of 2026 assistant posts or fewer, about as often as in human ones. The stronger signal is the plain words models leave out, like &lt;em&gt;very&lt;/em&gt; or &lt;em&gt;able&lt;/em&gt;, which people use all the time. Lists of three turned out to be the strongest single feature. And assistant posts carry about twice the headings of human ones (a median of 12.2 against 6.6 per 1,000 words).&lt;/p&gt;
&lt;h2&gt;
  
  
  A checklist that doesn't grade itself
&lt;/h2&gt;

&lt;p&gt;Next to the meter there's a checklist with four states. PASS and FAIL are what you'd expect, but FAIL is kept narrow on purpose: the wrapper lines a chat assistant puts around its answers, or several AI signals at once. My first version failed any em dash and any Title Case heading, and it failed every single one of the held-out human posts. The current rules pass 98% of them and still fail most of the Sonnet and GPT posts written on the same titles.&lt;/p&gt;

&lt;p&gt;The other two states are TELL, a pattern humans use too but assistants use more, and READ, something no regular expression can settle, like whether the text is generic or whether a fact got lost. A READ item isn't a pass. The model has to write a ruling on each one, and the loop only stops after two clean runs in a row.&lt;/p&gt;

&lt;p&gt;One more check exists because every rewriter I tested slipped in small invented details. A version number here, "in the same week" there. An invented detail reads as &lt;em&gt;more&lt;/em&gt; human, so no style check can see it. The skill diffs the rewrite against your draft and notes and fails on any number or link that isn't in them.&lt;/p&gt;
&lt;h2&gt;
  
  
  The result that humbled me
&lt;/h2&gt;

&lt;p&gt;I had fresh Claude Opus instances judge posts one at a time and give the probability that each one was AI. Human originals got 5% to 8%. Untouched assistant drafts got 90%.&lt;/p&gt;

&lt;p&gt;Then the rewrites. With every metric inside the human range they were still judged AI 12 times out of 12, at 82%. Adding samples of the author's writing didn't move it. What did move it was the author's own notes: what actually happened, real names, links, opinions. With those it came down to 37% and 53% in two rounds, and 10 of 24 posts were judged AI. On 8 fresh titles it went back up to 68%, all 8 judged AI.&lt;/p&gt;

&lt;p&gt;Twelve posts per row is small. The judge and the writers are also the same model family, so read that with care. But the direction was clear. Style alone doesn't do it. Your own material does. That's why the skill now asks for your notes before rewriting anything, and says plainly when it doesn't have them.&lt;/p&gt;

&lt;p&gt;What surprised me most, though, wasn't a number. Once it started working, the texts became fluid and familiar. I could read an AI-assisted document from start to finish without the filler, the known patterns or the heavy structure. That was what I was really after.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it's for, and what it isn't
&lt;/h2&gt;

&lt;p&gt;I know it can produce text that reads like a person wrote it, even when the AI wrote all of it. Even if you get great results that way, please don't. Passing off text the AI generated as your own, when you didn't write any of it, is wrong, and I'm not encouraging anyone to do it. The skill itself has a warning about this. It also doesn't beat detectors like GPTZero, which read token probabilities and never look at style.&lt;/p&gt;

&lt;p&gt;Using AI to end up with a better-written text is a good thing, though. It's very good at catching the mistakes and habits we all have. It works best on text you wrote yourself and want to polish, or text written with AI help that still has real human input in it. And for that project documentation nobody will write by hand, it should at least be easy to read when you need to get familiar with a concept or an internal workflow at the company.&lt;/p&gt;
&lt;h2&gt;
  
  
  When to use something else
&lt;/h2&gt;

&lt;p&gt;humanizer and stop-slop both do their job well, and I don't want to take credit away from them. If you want something lightweight, stop-slop is a short set of rules you can even paste into your custom instructions. humanizer has a very complete pattern list and a draft, audit and rewrite flow. Quirón makes more sense when you want a script to verify the result instead of the model grading itself, when you want to catch overshoot, or when you write in Spanish. I think it covers what they do and goes further, but try them and judge for yourself.&lt;/p&gt;

&lt;p&gt;On languages: it's tuned for English and Spanish, the two I speak (English barely, I'm still working on it). It also works in other languages and identifies the structural patterns, but the best results are in those two. If you speak something else, try it and tell me how it went. Contributions for new languages are welcome.&lt;/p&gt;

&lt;p&gt;Don't take my word for any of this. Run it on your own documentation and look at the measurements. The README was written with its help, so it's a quick way to see how it reads, and the install steps are there too.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ilien-dev" rel="noopener noreferrer"&gt;
        ilien-dev
      &lt;/a&gt; / &lt;a href="https://github.com/ilien-dev/quiron" rel="noopener noreferrer"&gt;
        quiron
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      AI humanizer skill for Claude Code, Codex and Cursor. Removes AI slop and the signs of AI writing, then measures the rewrite against human baselines.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&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%2Fraw.githubusercontent.com%2Filien-dev%2Fquiron%2FHEAD%2Fassets%2Fbrand%2Fhero%2Fquiron-hero-light.png" class="article-body-image-wrapper"&gt;&lt;img alt="Quirón: a centaur holding up an eight-point star" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Filien-dev%2Fquiron%2FHEAD%2Fassets%2Fbrand%2Fhero%2Fquiron-hero-light.png" width="720"&gt;&lt;/a&gt;
  
&lt;/p&gt;
&lt;p&gt;&lt;b&gt;English&lt;/b&gt; · &lt;a href="https://github.com/ilien-dev/quiron/README.es.md" rel="noopener noreferrer"&gt;Español&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Quirón: an AI humanizer skill that measures its own work&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;
  &lt;b&gt;An agent skill for Claude Code, Codex and Cursor. It strips the AI slop that marks prose as machine-written,&lt;br&gt;
  then checks the result against measured human writing instead of a hunch.&lt;/b&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/aa0af82478376b3704fed69a762bdb95b0cbf877807ad903622ecc70e227cc82/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332532307374646c69622532306f6e6c792d3230323031453f7374796c653d666c61742d737175617265"&gt;&lt;img alt="Python 3 standard library only" src="https://camo.githubusercontent.com/aa0af82478376b3704fed69a762bdb95b0cbf877807ad903622ecc70e227cc82/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f707974686f6e2d332532307374646c69622532306f6e6c792d3230323031453f7374796c653d666c61742d737175617265"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/dcd8454d5c5f304f9c5be3cad2c2115bfecbb7e314bf9dc88ac49e3e6e874ada/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7265676973746572732d626c6f6725323025433225423725323066696374696f6e25323025433225423725323065732d4235353633413f7374796c653d666c61742d737175617265"&gt;&lt;img alt="Registers: blog, fiction, Spanish" src="https://camo.githubusercontent.com/dcd8454d5c5f304f9c5be3cad2c2115bfecbb7e314bf9dc88ac49e3e6e874ada/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7265676973746572732d626c6f6725323025433225423725323066696374696f6e25323025433225423725323065732d4235353633413f7374796c653d666c61742d737175617265"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/d9d609052d3e061c457bb01ee5f1fd957cefa5f9fa3a69468ae163704c4e8e30/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f436c61756465253230436f64652d736b696c6c2d4637454544423f7374796c653d666c61742d737175617265266c6162656c436f6c6f723d323032303145"&gt;&lt;img alt="Claude Code skill" src="https://camo.githubusercontent.com/d9d609052d3e061c457bb01ee5f1fd957cefa5f9fa3a69468ae163704c4e8e30/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f436c61756465253230436f64652d736b696c6c2d4637454544423f7374796c653d666c61742d737175617265266c6162656c436f6c6f723d323032303145"&gt;&lt;/a&gt;
  &lt;a href="https://skills.sh/ilien-dev/quiron/quiron" rel="nofollow noopener noreferrer"&gt;&lt;img alt="skills.sh installs" src="https://camo.githubusercontent.com/8a9ccc726a4b44482e605277ea115b84aa25b9681b0a0fe17aac58b992444408/68747470733a2f2f736b696c6c732e73682f622f696c69656e2d6465762f717569726f6e"&gt;&lt;/a&gt;
  &lt;a href="https://quiron.ilien.dev/" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Website" src="https://camo.githubusercontent.com/aff22eb7ea1376459fee43bab6c14e575278d523b5dd6823836974ef1e115154/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f776562736974652d717569726f6e2e696c69656e2e6465762d4235353633413f7374796c653d666c61742d737175617265"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Hand it a draft and ask it to humanize the text, or to make a README sound less like
ChatGPT. It rewrites the prose, then measures the result. You get the rewrite and a
measurement that says whether it now reads like a person wrote it.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;With the &lt;a href="https://skills.sh" rel="nofollow noopener noreferrer"&gt;skills&lt;/a&gt; CLI, for Claude Code and every other agent it
supports, Gemini CLI and GitHub Copilot among them:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npx skills add ilien-dev/quiron&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;It needs no API key. It runs on the model you already use, and the scripts need nothing
beyond the Python 3 standard library.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;As a Claude Code plugin&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;Run these two commands inside Claude…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ilien-dev/quiron" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;How are you dealing with AI-generated documentation in your team? And if you try it, I'd like to know what it gets wrong.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>writing</category>
      <category>claude</category>
      <category>documentation</category>
    </item>
    <item>
      <title>A 200 OK is not an article: the bug that made me write my own web layer in Rust</title>
      <dc:creator>ilien</dc:creator>
      <pubDate>Tue, 22 Sep 2026 17:37:02 +0000</pubDate>
      <link>https://dev.to/ilien/a-200-ok-is-not-an-article-the-bug-that-made-me-write-my-own-web-layer-in-rust-34np</link>
      <guid>https://dev.to/ilien/a-200-ok-is-not-an-article-the-bug-that-made-me-write-my-own-web-layer-in-rust-34np</guid>
      <description>&lt;p&gt;There's a failure mode in agent tooling that never throws anything and I lost an afternoon to it before I understood what I was looking at. The fetch returns &lt;code&gt;200&lt;/code&gt; and the body isn't empty and the extractor finds text in that body and turns the text into clean Markdown. Then the model reads my clean Markdown and writes me a fluent summary that happens to be entirely wrong.&lt;/p&gt;

&lt;p&gt;The page was never the article. The page was the wall that says &lt;em&gt;"Checking your browser before you continue"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Every layer did its job, and no layer had the job of asking whether the page that arrived was the page I had asked for.&lt;/p&gt;

&lt;p&gt;That bug is why I spent the last stretch of my free time writing &lt;a href="https://svipall.ilien.dev/" rel="noopener noreferrer"&gt;Svipall&lt;/a&gt;, a local-first web layer for AI agents, in Rust. Below are the four things it does about it, with what I measured for each, including the measurements that didn't come out in my favour.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Nothing in the stack separates "the call worked" from "the page arrived"
&lt;/h2&gt;

&lt;p&gt;A retry won't help here because a retry is the same request, and escalating to a headless browser won't help either if nothing tells you that escalating is the thing this particular page needs. The only fix I found is that whatever fetched the page has to say what it thinks it got back, and that verdict has to travel with the page itself.&lt;/p&gt;

&lt;p&gt;So every fetch returns one, and a blocked page carries the evidence that produced it:&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;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"blocked_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vendor challenge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"wall_kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vendor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"wall_vendor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kpsdk.io"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"wall_evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"header x-kpsdk-ct"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Interstitial detected. A browser tier can run its script; a bare HTTP request cannot."&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;Twelve wall kinds, each one naming the move it implies. The field I care most about is &lt;code&gt;wall_evidence&lt;/code&gt; because it says why the verdict came out this way and gives you something to disagree with. If you can't audit the verdict you're back to trusting the verdict blindly and trusting it blindly is where my whole problem started.&lt;/p&gt;

&lt;p&gt;The honest caveat, which lives in the README too: the classification is heuristic, and a clean verdict doesn't prove that the records you wanted arrived intact. What it does is move the failure from silent to visible, and that's the whole claim I'm making for it.&lt;/p&gt;

&lt;p&gt;When a page doesn't arrive, escalating is the obvious move, but escalating on every fetch is how you turn a 200 ms fetch into a six-second fetch, forever:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  http      emulated Chrome TLS + HTTP/2, no browser      ~200 ms
    │       most pages stop here
    ▼
  browser   headless Chromium, runs the page's JS
    │
    ▼
  stealth   automation surfaces patched to match the identity
    │
    ▼
  real      visible-but-offscreen browser, persistent profile,
    │       pointer along curves, wheel scrolling
    ▼
  warm      bounded wait, captcha strategy loop runs inside it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I measured it from a single residential address with no proxy and took the median of three runs with the range: 26/31 on a public target list I didn't write, 7/12 on my own list of hard targets, 3/8 against four named commercial vendors.&lt;/p&gt;

&lt;p&gt;Three out of eight. That row stays in my README because a comparison table where every cell is green tells you more about the author than it does about the software. My raw logs are committed alongside it and they include the rounds of work that improved nothing and the rounds where a number went down.&lt;/p&gt;

&lt;h3&gt;
  
  
  On doing this responsibly
&lt;/h3&gt;

&lt;p&gt;This part isn't boilerplate, so I haven't put it at the bottom. Svipall evades anti-bot fingerprinting and that is all it does. It does not defeat authentication, it does not open paywalled content you haven't paid for and there is no cloud solver farm behind it. I wrote it for research and the job I wrote it for is reading pages a browser would happily show you, from a tool that isn't a browser, without the fetch failing silently.&lt;/p&gt;

&lt;p&gt;Being able to reach a system doesn't mean you're allowed to. You pick the addresses and you pick how hard to push and that puts the terms of service and the copyright and the data-protection law on you. The repo ships a &lt;a href="https://github.com/ilien-dev/svipall/blob/main/DISCLAIMER.md" rel="noopener noreferrer"&gt;DISCLAIMER&lt;/a&gt; that says so at length. &lt;code&gt;robots.txt&lt;/code&gt; handling is &lt;code&gt;warn&lt;/code&gt; by default and &lt;code&gt;obey&lt;/code&gt; on request. The crawler stops at whatever budget you set it: pages, tokens, traffic. If you point it at a hundred thousand pages just because you can, that one's on you.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The agent shouldn't have to rediscover the route every time
&lt;/h2&gt;

&lt;p&gt;I underestimated this one badly while I was building it. An agent that finds the right way into a site and then forgets that way has learned nothing at all, so tomorrow the agent climbs the same ladder again: same seconds burned, same reputation spent with the same site, same answer at the end of it.&lt;/p&gt;

&lt;p&gt;So the route is remembered per domain, and the route survives the session. Two supporting observations can promote a working tier, and the next visit then starts there instead of at the bottom. Cooldowns are stored the same way, and so is per-exit health, and so are the walls a site is known to serve. &lt;code&gt;svipall status&lt;/code&gt; prints the lot of it.&lt;/p&gt;

&lt;p&gt;The same memory runs deeper than the tier. Captcha strategies are ordered by what actually worked on that route, so the loop tries the winner first and a strategy that declines costs no attempt. A &lt;code&gt;schema&lt;/code&gt; you name is fingerprinted per domain, and when the site gets redesigned the selector is relocated by similarity and comes back marked &lt;code&gt;healed&lt;/code&gt;. Cookies from a &lt;code&gt;web_login&lt;/code&gt; live in a profile you can export. &lt;code&gt;web_notes&lt;/code&gt; is key-value memory that outlives the session, for whatever the agent worked out that the tool doesn't model.&lt;/p&gt;

&lt;p&gt;The effect on an agent loop is fewer steps, and fewer steps is the same thing as fewer tokens. In a paired run, on the 109 pairs where both arms returned useful content, the automatic ladder spent 368 s against native browsing's 735 s. That difference isn't only fast refusals.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The tokens
&lt;/h2&gt;

&lt;p&gt;I fetched one page three ways, just now, on this machine. English Wikipedia, &lt;em&gt;Rust (programming language)&lt;/em&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What I asked for&lt;/th&gt;
&lt;th&gt;Characters&lt;/th&gt;
&lt;th&gt;Share of the raw HTML&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;extraction: "html"&lt;/code&gt;, the raw markup&lt;/td&gt;
&lt;td&gt;1,048,788&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;default, extracted Markdown&lt;/td&gt;
&lt;td&gt;106,266&lt;/td&gt;
&lt;td&gt;10.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;query: "borrow checker ownership"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;48,486&lt;/td&gt;
&lt;td&gt;4.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Roughly 262k → 27k → 12k tokens at four characters per token. The character counts are measured; the token figures are the usual estimate and not a tokenizer run.&lt;/p&gt;

&lt;p&gt;Ninety per cent of that page was navigation, reference markup and the apparatus Wikipedia wraps around its prose. Every character of that ninety per cent was billable, and every character of it sat between the model and the part that mattered.&lt;/p&gt;

&lt;p&gt;Boilerplate removal has a number attached to it: median ROUGE-LSum F1 of 0.920 over the 3,975 gradable pages of the SIGIR-23 gold standard, against 0.732 with the boilerplate removal switched off. And then the row I'd rather not print, which is that &lt;code&gt;readability&lt;/code&gt; scores 0.963 on that same corpus and &lt;code&gt;trafilatura&lt;/code&gt; 0.958. They beat me. Both of them are excellent and both of them are in my comparison table for exactly that reason.&lt;/p&gt;

&lt;p&gt;Where I spend the difference is &lt;code&gt;query=&lt;/code&gt;. It keeps only the blocks that lexically match your words using BM25. No embedding model and no API call, so nothing here bills you twice for reading one page. How much it saves depends on the query and not on the page. On two long articles the query &lt;code&gt;"robots.txt"&lt;/code&gt; left 6% and 13% of the text. The query &lt;code&gt;"history of scraping"&lt;/code&gt; left 88% of it. So the query should name the fact you want.&lt;/p&gt;

&lt;p&gt;Then there are the duplicates. I find near-duplicates with SimHash and a banded index, and &lt;code&gt;web_fetch_many&lt;/code&gt; reports &lt;code&gt;corroboration&lt;/code&gt;, which is how many distinct documents your set actually is, marking each copy with &lt;code&gt;same_text_as&lt;/code&gt;. Eight sources that are one press release reprinted eight times aren't eight sources, and an agent has no way to know that unless something says so.&lt;/p&gt;

&lt;p&gt;Then there's the plumbing. &lt;code&gt;out_file&lt;/code&gt; writes to disk and hands back a path, measured at a 418-character response in place of the 34,746-character page it wrote, and &lt;code&gt;schema: "auto"&lt;/code&gt; reads a listing's own repeated structure and returns named columns from one parse, with no model involved.&lt;/p&gt;

&lt;p&gt;One rule costs me recall and stays in anyway: hidden text never reaches the model, so anything behind &lt;code&gt;display:none&lt;/code&gt;, &lt;code&gt;left:-9999px&lt;/code&gt; or &lt;code&gt;aria-hidden&lt;/code&gt; gets dropped before extraction. A paragraph parked off-screen reads to an agent exactly like the article does, and that paragraph is the entire prompt-injection surface. There are 123 phrases a human annotator marked as real content sitting behind that rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Nobody should have to count how many times they've hit a site
&lt;/h2&gt;

&lt;p&gt;A hosted scraper rotates through a pool of addresses, so the question of how much one address has asked a host never comes up. Run the thing locally and your home connection is the entire pool. An agent in a loop has no idea how many calls it has already made against a domain this afternoon, and neither do you, and the site absolutely does.&lt;/p&gt;

&lt;p&gt;I learned this from my own benchmark. I ran the target lists against one residential address several times in a day, and a cell that had been passing stopped passing. Nothing in the tool had changed. I had simply spent the address.&lt;/p&gt;

&lt;p&gt;So there's a ledger now. Every visit costs what its tier costs, doubled when the page comes back walled:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;http&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;browser&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;stealth&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;real&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;warm&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A count of visits would be the wrong unit. An HTTP GET and a headful browser sitting through a twenty-second challenge are not the same event to a host that scores addresses. The budget is 250 by default and it decays with a six-hour half-life instead of resetting at midnight, which makes it a rate and not a daily allowance: a steady spend settles at about 29 points an hour.&lt;/p&gt;

&lt;p&gt;Under 70% of the budget nothing happens. Between 70% and 100% the pacer stretches the gap between requests, up to four times. Over it, the fetch is declined before a packet leaves the machine, with &lt;code&gt;blocked_reason: "address_budget"&lt;/code&gt;, the number of seconds until it isn't, and the way out.&lt;/p&gt;

&lt;p&gt;The part I'd defend hardest: &lt;strong&gt;your own address is a key in that ledger like any other&lt;/strong&gt;, which is the whole point, because the case that broke my benchmark had no proxy involved at all. Pacing is keyed by &lt;code&gt;(domain, exit)&lt;/code&gt; too, so the gap comes from the host's own latency and refusals instead of a global constant: 100 ms to 2 s on &lt;code&gt;http&lt;/code&gt;, 400 ms to 5 s on the browser tiers.&lt;/p&gt;

&lt;p&gt;None of this makes a site trust you. It stops your agent from spending goodwill it doesn't know it has.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is, and what else already exists
&lt;/h2&gt;

&lt;p&gt;It's a single Rust binary that speaks MCP (29 tools) for Claude Code, Claude Desktop and Cursor. It works as a CLI and it turns into a local REST API under &lt;code&gt;svipall serve&lt;/code&gt; so any language can drive it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;svipall fetch https://docs.example/api &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"rate limits"&lt;/span&gt;
svipall fetch https://shop.example/listing &lt;span class="nt"&gt;--schema&lt;/span&gt; auto    &lt;span class="c"&gt;# rows from a page you've never seen&lt;/span&gt;
svipall crawl https://docs.example/ &lt;span class="nt"&gt;--pages&lt;/span&gt; 50 &lt;span class="nt"&gt;--out&lt;/span&gt; pages.csv
svipall search &lt;span class="s2"&gt;"rust async runtime"&lt;/span&gt; &lt;span class="nt"&gt;--engine&lt;/span&gt; all            &lt;span class="c"&gt;# no API key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's nothing to install underneath it and nothing to sign up for: no Node, no Python, no API key, no telemetry. Captchas get attempted locally across 15 widget families and 11 answer modalities, and when none of that works the page parks in a dashboard at &lt;code&gt;localhost:8787/human&lt;/code&gt; so a person can answer it once. There's no paid solver in the loop and no path where your pages reach my server, since I don't run one.&lt;/p&gt;

&lt;p&gt;I'm not going to pretend the field is empty. &lt;a href="https://github.com/firecrawl/firecrawl" rel="noopener noreferrer"&gt;Firecrawl&lt;/a&gt; is the hosted API you can call from a lambda. If you live in Python, &lt;a href="https://github.com/unclecode/crawl4ai" rel="noopener noreferrer"&gt;Crawl4AI&lt;/a&gt; is mature and ships a Docker server. &lt;a href="https://github.com/D4Vinci/Scrapling" rel="noopener noreferrer"&gt;Scrapling&lt;/a&gt; does adaptive parsing with session and proxy control. For plain browser automation over MCP the reference answer is still &lt;a href="https://github.com/microsoft/playwright-mcp" rel="noopener noreferrer"&gt;Playwright MCP&lt;/a&gt;. All four are in the repo's comparison table, and if you want a URL you can hit from a serverless function, use a hosted service.&lt;/p&gt;

&lt;p&gt;What Svipall adds is a combination I couldn't find anywhere in one place. The fetch tells you whether the page arrived, the route to that page is something the agent learns once, and the tokens get cut during extraction instead of after it. All of that runs on hardware you own. That comparison in the repo describes documented scope on a stated date. It is not a head-to-head benchmark, so validate your own target pages before you switch anything.&lt;/p&gt;

&lt;p&gt;My receipts live in &lt;a href="https://svipall.ilien.dev/docs/proof" rel="noopener noreferrer"&gt;the proof page&lt;/a&gt;, each one with the command that reproduces it: 1,216 tests, 160/160 automation-tell probes clean over five browser passes, 8/8 identity coherence checks, and a delivery snapshot of 348/459 calls (75.82%) over 48 URLs, unattended.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/ilien-dev/svipall/main/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AGPL-3.0, extraction engine MIT/Apache-2.0 so anything can depend on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://svipall.ilien.dev/" rel="noopener noreferrer"&gt;svipall.ilien.dev&lt;/a&gt;&lt;/strong&gt; · &lt;a href="https://github.com/ilien-dev/svipall" rel="noopener noreferrer"&gt;source on GitHub&lt;/a&gt; · &lt;a href="https://svipall.ilien.dev/docs/limits" rel="noopener noreferrer"&gt;what it can't do&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built this, and I'd like one answer in the comments: how does your agent currently find out that a fetch returned a wall instead of a page? Every answer I've gotten so far is some version of "it doesn't, I noticed later", and if that's the state of the art, the silent &lt;code&gt;200&lt;/code&gt; is a bigger hole in agent tooling than the scraping part anyone argues about.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
