<?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: INTFRAME</title>
    <description>The latest articles on DEV Community by INTFRAME (@intframe).</description>
    <link>https://dev.to/intframe</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%2F4078029%2F1c9fc8fe-bfa8-4837-be9f-bb879d1f7064.png</url>
      <title>DEV Community: INTFRAME</title>
      <link>https://dev.to/intframe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/intframe"/>
    <language>en</language>
    <item>
      <title>We measured the LLM signature in 398,296 Korean journal abstracts</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Tue, 01 Sep 2026 12:37:01 +0000</pubDate>
      <link>https://dev.to/intframe/we-measured-the-llm-signature-in-398296-korean-journal-abstracts-2g11</link>
      <guid>https://dev.to/intframe/we-measured-the-llm-signature-in-398296-korean-journal-abstracts-2g11</guid>
      <description>&lt;p&gt;Since 2023 several studies have shown that the vocabulary of English scholarly writing changed after ChatGPT. Kobak et al. (&lt;a href="https://doi.org/10.1126/sciadv.adt3813" rel="noopener noreferrer"&gt;Science Advances, 2025&lt;/a&gt;) called it &lt;em&gt;excess vocabulary&lt;/em&gt;: a word's frequency above the trend it was on before 2023. English abstracts started overusing &lt;em&gt;delve&lt;/em&gt;, &lt;em&gt;underscore&lt;/em&gt;, &lt;em&gt;intricate&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;One Korean study exists: Koo, Kim and Kim (&lt;a href="https://doi.org/10.29403/li.30.1.1" rel="noopener noreferrer"&gt;Language and Information, 2026&lt;/a&gt;) ran an excess-vocabulary comparison over ~880,000 KCI humanities and social-science abstracts, projecting 2024 from 2021-2022. They found a sharp rise in the &lt;em&gt;English&lt;/em&gt; abstracts and only modest movement in the Korean ones, and their observation ends in 2024 — before the Korean onset we measure. Korean had not been measured at the point where it actually moves, and the method does not transfer for free: Korean is agglutinative, so whitespace tokens are the wrong unit, and the verbs that carry register are built by fusing a Sino-Korean noun with a verbalising suffix.&lt;/p&gt;

&lt;p&gt;So we rebuilt the statistic over morphological lemma-tag units and ran it on the full openly harvestable record of the Korea Citation Index: &lt;strong&gt;398,296 abstracts from 2,282 journals, 2018 to August 2026&lt;/strong&gt;, with 47,165 Vietnamese abstracts as an exploratory second case.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the numbers say
&lt;/h2&gt;

&lt;p&gt;The verb 시사하다 (&lt;em&gt;sisahada&lt;/em&gt;, "suggest, imply") appears in &lt;strong&gt;21.4%&lt;/strong&gt; of 2026 abstracts. Extrapolating its 2018–2022 trend predicts &lt;strong&gt;5.3%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the other direction, plain-register expressions fell: 알아보다 ("look into") and 도움이 되다 ("be helpful") dropped to about a quarter of their trend.&lt;/p&gt;

&lt;p&gt;The time course is sharp. Nothing in 2023. Onset in the second half of 2024. A steep rise through 2025. Flattening in mid-2026.&lt;/p&gt;

&lt;p&gt;Under stated identification assumptions, the conditional lower bound on the share of LLM-processed Korean abstracts is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Statistic&lt;/th&gt;
&lt;th&gt;2024&lt;/th&gt;
&lt;th&gt;2025&lt;/th&gt;
&lt;th&gt;2026&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single word&lt;/td&gt;
&lt;td&gt;3.5%&lt;/td&gt;
&lt;td&gt;10.5%&lt;/td&gt;
&lt;td&gt;16.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Split-half word set&lt;/td&gt;
&lt;td&gt;7.8%&lt;/td&gt;
&lt;td&gt;20.6%&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;33.0%&lt;/strong&gt; (95% CI 30.8–35.4)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  This is not a detector
&lt;/h2&gt;

&lt;p&gt;It cannot tell you whether &lt;em&gt;your&lt;/em&gt; paper was written by a model, and it is not built to. It measures a distribution over a corpus and reports a floor: the true share is at least this, under assumptions we state and test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ruling things out
&lt;/h2&gt;

&lt;p&gt;A number this large invites obvious objections, so we tried to kill it several ways.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"The fields just changed what they write about."&lt;/strong&gt; We paired every 2026 abstract with the base-period abstract from the &lt;em&gt;same journal&lt;/em&gt; whose title is most similar (character-bigram cosine, IDF-weighted). The matched difference is 34.1 pp against 34.0 pp unmatched. Topic matching barely moves it. We also had three independent language-model annotators classify each marker lemma as style or topic; restricting the set to lemmas all three call style still leaves 14.7 pp.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"It's translation from English."&lt;/strong&gt; The surface marks of translated Korean (the 것이다 construction, ~에 대한, explicit plural 들) &lt;em&gt;fell&lt;/em&gt; over the same period while the markers rose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"It's a statistical artefact of extrapolation."&lt;/strong&gt; We ran the identical procedure with pre-ChatGPT target years, at every extrapolation horizon the data allow. The largest set-statistic value any placebo produced was 2.9 pp. The 2026 value is more than eleven times that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"You got lucky with one split."&lt;/strong&gt; The set is chosen on one half of the journals and measured on the other. We repeated the split 100 times with different journal hashes: median 32.4 pp, 5th–95th percentile 31.2–33.8, set sizes 38–56 lemmas.&lt;/p&gt;

&lt;h2&gt;
  
  
  The English abstracts of the same articles
&lt;/h2&gt;

&lt;p&gt;Most KCI articles carry an English abstract alongside the Korean one. That gives an article-fixed comparison: same authors, same journal, same paper, two languages.&lt;/p&gt;

&lt;p&gt;The English side moves a year earlier — the excess is already there in 2023. And among articles whose English abstract carries none of the period's English markers, the Korean shift is still present, at 30–66% of its uncorrected rate after correcting for the English indicator's own sensitivity.&lt;/p&gt;

&lt;p&gt;So the Korean change is not simply a by-product of a model-written English abstract, though the size of the effect does shrink under that correction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everything reproduces
&lt;/h2&gt;

&lt;p&gt;The paper (28 pages), the analysis code, per-year document-frequency tables for Korean, English and Vietnamese, the journal panels, and 1,437 control abstracts we generated with models from three providers are all deposited:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.5281/zenodo.22110398" rel="noopener noreferrer"&gt;https://doi.org/10.5281/zenodo.22110398&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unzip it into an empty directory, run &lt;code&gt;reproduce.sh&lt;/code&gt;, and every table and figure in the paper is regenerated from the shipped results. It ends in &lt;code&gt;REPRODUCE_OK&lt;/code&gt; or it fails loudly. Raw abstract texts are not redistributed for copyright reasons, but the harvesting scripts against the public KCI and VJOL OAI-PMH endpoints are included.&lt;/p&gt;

&lt;p&gt;Two free tools built from the same measurements, in Korean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://os.intframe.com/report/ai-style-dictionary-ko" rel="noopener noreferrer"&gt;AI style dictionary&lt;/a&gt; — which words rose and fell, with base rates&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://os.intframe.com/report/ai-style-check-ko" rel="noopener noreferrer"&gt;Style checker&lt;/a&gt; — paste text, see which markers it contains (again: a base-rate lookup, not a verdict)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this does not settle
&lt;/h2&gt;

&lt;p&gt;The attribution assumption is the weak point, and we say so in the paper. Observational data cannot rule out that some contemporaneous stylistic drift, entirely unrelated to language models, produced part of this. What we can do is show the size of the change, its timing, its resistance to topic and translation controls, and the level of noise the method produces when nothing is happening. That is why the headline number is a &lt;em&gt;conditional lower bound&lt;/em&gt;, not a prevalence.&lt;/p&gt;

&lt;p&gt;The annotation of markers as style-versus-topic was itself done by language models, which is circular in a way we would rather fix with human annotators. That is the first thing on the list.&lt;/p&gt;

&lt;p&gt;The manuscript is under review at &lt;em&gt;Scientometrics&lt;/em&gt;. Comments, replications and objections are welcome — the data is right there.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>datascience</category>
      <category>research</category>
      <category>nlp</category>
    </item>
    <item>
      <title>Trust, then verify, then verify again</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:50:06 +0000</pubDate>
      <link>https://dev.to/intframe/trust-then-verify-then-verify-again-51i8</link>
      <guid>https://dev.to/intframe/trust-then-verify-then-verify-again-51i8</guid>
      <description>&lt;p&gt;Generative systems fail differently from normal software. They do not crash. They produce something plausible, fluent and occasionally wrong, at scale, with a straight face. Shipping their output to the outside world without machinery in between is negligence. Ours pass three walls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wall one: evidence gates
&lt;/h2&gt;

&lt;p&gt;Every automated claim must carry its receipts, and the receipts are structured, not vibes:&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;"claim"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"business site has no online booking flow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&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;"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;"https://example-biz.com/reserve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fetched_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-11T02:14:09Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"check"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"booking CTA selectors absent in rendered DOM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"screenshot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:9f2c11ab..."&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;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pass"&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;No evidence object, no pass. Fuzzy match where the rule demands exact, no pass. It is astonishing how many hallucinations die at the simple demand: show me where you read that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wall two: the identity gate, and the bug that taught us humility
&lt;/h2&gt;

&lt;p&gt;A pipeline that writes about real businesses must first prove it is looking at the &lt;em&gt;right&lt;/em&gt; business. Our first identity gate compared the business name against the domain name and skipped anything that did not match. Reasonable, and catastrophically wrong in a market where businesses have Korean names and Latin-alphabet domains. The gate was silently discarding 98% of legitimate targets. The fix was to stop trusting the domain string and verify identity from the page body instead, name, address, phone cross-checked against the record. Pass rate went from 2% to 71% with zero wrong-entity incidents since. The lesson generalized: a gate that fails closed can be just as wrong as a gate that fails open, and only measurement tells you which failure you have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wall three: adversarial second passes
&lt;/h2&gt;

&lt;p&gt;The cheapest quality upgrade we know is asking a second model instance to attack the first one's output. Not improve. Refute. The reviewer prompt states that its job is to find the flaw, and that when uncertain it should call the finding refuted, because a missed defect costs us more than a discarded one. A reviewer whose only job is finding flaws finds the ones a collaborative reviewer politely overlooks. Findings that survive are the ones worth acting on.&lt;/p&gt;

&lt;p&gt;Behind all three walls sits the regression suite, about 3,900 cases, expected failures zero, one case added per incident. None of this slows the pipeline meaningfully. All of it together is what lets us point generative machinery at real-world consequences and sleep.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>qa</category>
    </item>
    <item>
      <title>Evals are the unit tests of prompts</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:48:35 +0000</pubDate>
      <link>https://dev.to/intframe/evals-are-the-unit-tests-of-prompts-23d3</link>
      <guid>https://dev.to/intframe/evals-are-the-unit-tests-of-prompts-23d3</guid>
      <description>&lt;p&gt;Prompt changes used to ship on vibes: run three examples, looks better, merge. Then a "small wording improvement" quietly broke number formatting in a pipeline for four days. Now a prompt is code, and code does not merge without tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The golden set grows out of scars
&lt;/h2&gt;

&lt;p&gt;Each pipeline carries a labeled set of input/expected pairs. Nobody sat down to author it; it accreted, one case per incident, each test a way the system once embarrassed itself. Our largest text pipeline currently runs about 3,900 cases on every change with an expected failure count of exactly zero. The zero is non-negotiable. A suite that tolerates a few failures is a suite nobody reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  LLM-as-judge, with handcuffs
&lt;/h2&gt;

&lt;p&gt;Half our cases have no single right answer, so a model judges them. A model judge is a biased witness, and we treat it like one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Verdict rules:
- Compare A and B for the stated criteria only. Do not reward length.
- TIE is a valid verdict. Prefer TIE over a coin flip.
- Output exactly one line: WINNER: A | B | TIE, then one reason.

(Runner: every pair is judged twice with A and B swapped.
 Only a verdict that survives the swap is recorded. ~11% do not.)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Position bias is real.&lt;/strong&gt; Judges favor the first answer. The swap-and-agree protocol deletes that class of noise, at the cost of doubling judge calls. Worth it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Length bias is real.&lt;/strong&gt; Unconstrained judges reward word count. The rubric says so explicitly, and outputs are capped before judging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-preference is real.&lt;/strong&gt; A model grades its own family generously, so the judge is a different family than the generator wherever the stakes are high.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Agents get a harder bar
&lt;/h2&gt;

&lt;p&gt;For agent tasks we do not measure pass@k, whether one of k attempts succeeds. We measure the opposite: run the same task k times and require every run to succeed before the agent earns autonomy on that task class. A workflow that succeeds four times out of five is not 80% done. It is a pager duty rotation you have not scheduled yet.&lt;/p&gt;

&lt;p&gt;The payoff is boring in the best way: prompt refactors, model swaps and provider migrations became routine changes with a red/green answer, instead of leaps of faith followed by a week of watching logs.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>evals</category>
    </item>
    <item>
      <title>Small models where they win</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:45:00 +0000</pubDate>
      <link>https://dev.to/intframe/small-models-where-they-win-27f4</link>
      <guid>https://dev.to/intframe/small-models-where-they-win-27f4</guid>
      <description>&lt;p&gt;The question "which model should we use" is usually malformed. For classification-shaped work, the answer is two models and a threshold.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern: label big, serve small
&lt;/h2&gt;

&lt;p&gt;When a pipeline needs to classify at volume, categories, spam, intent, language, we do not call a frontier model per item. We call it once per &lt;em&gt;dataset&lt;/em&gt;: have the big model label 30,000 historical examples with a carefully engineered rubric, spot-check a sample by hand, then distill those labels into a small model that runs on our own box. The frontier model's judgment gets compiled into something that serves at 15 ms for the price of electricity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The router handles the hard tail
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;small&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// ~15 ms, on-box, effectively free&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;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.92&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;frontier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// ~1.5 s, the 7% that deserve it&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On our distribution, 93% of items clear the threshold locally and agreement with the frontier model on that slice is above 99%. The ambiguous 7% get the expensive brain. Blended cost lands two orders of magnitude below calling the frontier model on everything, and p50 latency drops from seconds to milliseconds, which matters when the classifier sits inside a user-facing request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where fine-tuning actually loses
&lt;/h2&gt;

&lt;p&gt;We keep re-testing fine-tuning and it keeps losing to prompting for our shapes of work. Style, format and policy compliance are handled better by a versioned prompt with few-shot examples riding a warm prefix cache: changes deploy in minutes, roll back in seconds, and A/B cleanly. A fine-tune is a build artifact with a days-long iteration loop, and it goes stale every time the base model improves, which lately is often. The distillation pattern above is the exception that earns it, and even there we distill into small open-weight models we host, so the artifact is ours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The drift tax
&lt;/h2&gt;

&lt;p&gt;The distilled model is frozen; the world is not. So the router double-writes a 1% shadow sample to the frontier model continuously, and disagreement above a threshold pages us to re-label and re-distill. Small models are cheap to run and easy to forget. The shadow sample is what keeps "we automated this last spring" from decaying into "we have been silently misclassifying since spring".&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>performance</category>
    </item>
    <item>
      <title>Our AI coworkers run on files</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:38:19 +0000</pubDate>
      <link>https://dev.to/intframe/our-ai-coworkers-run-on-files-31ok</link>
      <guid>https://dev.to/intframe/our-ai-coworkers-run-on-files-31ok</guid>
      <description>&lt;p&gt;The most consequential architecture decision we made for our agents is also the least glamorous: &lt;strong&gt;agent memory is plain files in a git-friendly tree.&lt;/strong&gt; One note per fact, frontmatter for machines, prose for the model:&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="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;payment-retry-policy&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;queue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;must&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;never&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;re-attempt&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;captured&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;payment"&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;project&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;The retry worker treats 'captured' as terminal. The 2025-11 double-charge&lt;/span&gt;
&lt;span class="s"&gt;incident came from retrying on a timeout after capture succeeded.&lt;/span&gt;
&lt;span class="na"&gt;Related&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;refund-flow&lt;/span&gt;&lt;span class="pi"&gt;]]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="pi"&gt;[[&lt;/span&gt;&lt;span class="nv"&gt;idempotency-keys&lt;/span&gt;&lt;span class="pi"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A session starts by reading a 40-line index, one line per note, not the 400 KB corpus. The model picks the two or three notes whose description matches the task and opens exactly those. Retrieval is a decision made by something that can reason, against descriptions a human curated, and it is fully explainable after the fact: here is the index line, here is the file, here is the sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not embeddings
&lt;/h2&gt;

&lt;p&gt;We prototyped the same corpus behind a vector store and killed it in two weeks. When an agent behaved oddly there was no file to open, only similarity scores to second-guess. Corrections meant re-embedding instead of editing a paragraph. And nothing about a few hundred curated notes needs approximate nearest neighbors; the whole index fits in one screen of context. Embeddings solve scale and fuzziness. Curated memory has neither problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caps matter more than schemas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The rules file fits on one screen, and adding a line requires deleting a line.&lt;/li&gt;
&lt;li&gt;The state file has a hard line cap; a cron sweeps anything stale into an archive that recall can still search.&lt;/li&gt;
&lt;li&gt;Every note links related notes with [[name]] references, so the graph is walkable with grep alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Memory that grows without bound stops being read, and memory that is not read is decoration. The caps are what keep the system honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boundary that makes it safe
&lt;/h2&gt;

&lt;p&gt;Agents propose, gates dispose. An agent can draft anything into its tree, but irreversible actions, deploys, deletions, payments, outbound messages, pass through the same mechanical gates as our cron jobs: whitelists, caps, ledgers, human approval where it counts. We do not ask the model to remember to be careful. Care is compiled into the pipeline around it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>The night shift is a fleet of models</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:36:48 +0000</pubDate>
      <link>https://dev.to/intframe/the-night-shift-is-a-fleet-of-models-2ndg</link>
      <guid>https://dev.to/intframe/the-night-shift-is-a-fleet-of-models-2ndg</guid>
      <description>&lt;p&gt;At some point the number of scheduled jobs, monitors and pipelines crossed the line where a human could still be the router. Our answer was not one big autonomous agent. It was a fleet of small ones with a deterministic script holding the leash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code owns control flow, models own content
&lt;/h2&gt;

&lt;p&gt;The orchestrator is ordinary code: it decides what runs, in what order, with what concurrency, and what happens on failure. Models fill in the steps. A planner turns an objective into a task list. Workers execute tasks inside tool sandboxes with step budgets. A verifier, prompted to refute rather than to help, attacks the workers' output. A reporter compresses the surviving results for humans. Every role writes a JSONL transcript, so any night's work can be replayed step by step the next morning.&lt;/p&gt;

&lt;p&gt;The one architectural rule: models never talk to each other directly. They talk through the orchestrator, which means every hop is logged, budgeted and interruptible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outbound is the dangerous direction
&lt;/h2&gt;

&lt;p&gt;The risky side of ops automation is not reading, it is writing. A bot that can message a human at 4 AM will, one day, message them forty times at 4 AM. Every outbound path in our fleet, human or API, goes through one gate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;WHITELIST&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;refuse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;recipient&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&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;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;countToday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&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;DAILY_CAP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;refuse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cap&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ledger&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="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;   &lt;span class="c1"&gt;// written BEFORE sending&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;to&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;A whitelist of recipients, a daily cap enforced in code, a ledger written before the send, and one gate shared by every sender. Ten scripts sharing one gate beat ten scripts with ten opinions about etiquette.&lt;/p&gt;

&lt;h2&gt;
  
  
  Silence detection over failure alerts
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# the watchdog does not listen for errors. it demands proof of life.&lt;/span&gt;
find /opt/health &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.ok'&lt;/span&gt; &lt;span class="nt"&gt;-mmin&lt;/span&gt; +90 &lt;span class="nt"&gt;-print&lt;/span&gt;    &lt;span class="c"&gt;# stale heartbeat = incident&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cron jobs fail loudly only in fairy tales. In production they fail by not running: a typo in a schedule, a dead virtualenv, a server clock in a timezone the author did not expect. Our watchdog expects heartbeats and treats absence as the alarm. Since that inversion, the class of incident where something died silently for a week has simply disappeared.&lt;/p&gt;

&lt;p&gt;The rule of thumb we settled on: automation earns autonomy in proportion to how mechanically its blast radius is capped. Write the gate first, then let the fleet loose.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>ops</category>
    </item>
    <item>
      <title>Grep first, embed when it earns it</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:30:07 +0000</pubDate>
      <link>https://dev.to/intframe/grep-first-embed-when-it-earns-it-530a</link>
      <guid>https://dev.to/intframe/grep-first-embed-when-it-earns-it-530a</guid>
      <description>&lt;p&gt;Retrieval shows up twice in our stack and the two cases got opposite architectures. Agent memory, a few hundred curated notes, runs on files and grep. User-facing search, tens of thousands of multilingual records, runs on hybrid embeddings. The dividing line is worth spelling out, because the industry default is to buy a vector database for both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where grep wins
&lt;/h2&gt;

&lt;p&gt;Agent memory is small, curated, and read by something that can already reason. Each note carries a one-line description in an index file; the agent reads the 40-line index, decides what is relevant, and opens two or three files. Debugging is opening the exact file the agent read. Correcting is editing a paragraph. When we prototyped the same corpus behind embeddings, retrieval became a probability distribution we had to interrogate, updates required re-embedding, and nobody could answer the only question that matters in an incident: why did it read that?&lt;/p&gt;

&lt;h2&gt;
  
  
  Where embeddings earn it
&lt;/h2&gt;

&lt;p&gt;Search across 40,000 records in mixed Korean, English and Vietnamese is a different animal. Users type "cheap spa near the river" and the record says "riverside massage, budget". Keywords miss; vectors catch. But pure dense retrieval lost to a hybrid on our own eval set, so hybrid is what runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;kw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bm25&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;              &lt;span class="c1"&gt;// exact terms, names, numbers&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// paraphrase, cross-lingual&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dedupe&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;dn&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;ranked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;llmRerank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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;30&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// one cheap model call&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;               &lt;span class="c1"&gt;// what actually enters the context&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a 400-query labeled set built from real search logs, recall@20 was 0.71 for keyword alone, 0.76 for dense alone, 0.90 for the hybrid with rerank. The rerank call costs a fraction of a cent and fixed the last mile better than any embedding model swap we tried.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chunking is where quality actually lives
&lt;/h2&gt;

&lt;p&gt;Fixed 512-token windows sever tables from their headers and answers from their questions. We chunk by document structure, headings and list boundaries, and every chunk carries its source path and byte offsets, so anything the model quotes is one click from the sentence it came from. A retrieval system that cannot cite its sources is a rumor generator with good latency.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>search</category>
    </item>
    <item>
      <title>Write prompts for the cache, not the reader</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:28:36 +0000</pubDate>
      <link>https://dev.to/intframe/write-prompts-for-the-cache-not-the-reader-5063</link>
      <guid>https://dev.to/intframe/write-prompts-for-the-cache-not-the-reader-5063</guid>
      <description>&lt;p&gt;Modern LLM APIs cache the key-value state of a prompt prefix. Send the same opening tokens twice and the second call is billed at roughly a tenth of the input price and returns noticeably faster. This one fact reorganized how we write every high-volume prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule
&lt;/h2&gt;

&lt;p&gt;Everything stable goes first, byte-identical on every call: system rules, output schema, few-shot examples, reference tables. Everything volatile goes last: the task, the retrieved documents, the user text. The cache breaks at the first differing byte, so a single timestamp in the header poisons the whole prefix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ system rules          ]  stable, versioned      ─┐
[ output schema         ]  stable, versioned       ├─ cached prefix, ~91% of tokens
[ 12 few-shot examples  ]  stable, versioned      ─┘
[ retrieved context     ]  volatile ──────────────── paid in full
[ the actual task       ]  volatile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Measured on one of our extraction pipelines
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;prompt layout&lt;/th&gt;
&lt;th&gt;prefix reuse&lt;/th&gt;
&lt;th&gt;cost per 1k calls&lt;/th&gt;
&lt;th&gt;p50 latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;task first, rules inline, timestamp in header&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;$31.40&lt;/td&gt;
&lt;td&gt;4.1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stable prefix, task last&lt;/td&gt;
&lt;td&gt;91%&lt;/td&gt;
&lt;td&gt;$6.90&lt;/td&gt;
&lt;td&gt;1.8 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same model, same accuracy, 78% cheaper and twice as fast, for a diff that only moved paragraphs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational consequences
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The prefix is versioned like an API.&lt;/strong&gt; Editing it is a deliberate release, because every edit is a fleet-wide cache flush and a visible cost spike. We tag prompts with a version constant and roll them like schema migrations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch queues are sorted by prefix.&lt;/strong&gt; Requests sharing a prefix run adjacently so the cache stays warm instead of being evicted between hits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hit rate is a logged metric.&lt;/strong&gt; The API reports cached token counts per call; we graph them. A silent drop in cache hit rate has caught two accidental prompt edits before the invoice did.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Randomness is banned from the prefix.&lt;/strong&gt; No timestamps, no request IDs, no dict-ordering roulette. Serialization is canonical, keys sorted, for the same reason your build system wants deterministic inputs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this touches quality. It is pure systems hygiene applied to a resource most teams do not know they are wasting.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>performance</category>
    </item>
    <item>
      <title>An agent is a while loop with taste</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:24:09 +0000</pubDate>
      <link>https://dev.to/intframe/an-agent-is-a-while-loop-with-taste-31j7</link>
      <guid>https://dev.to/intframe/an-agent-is-a-while-loop-with-taste-31j7</guid>
      <description>&lt;p&gt;A meaningful share of our daily work is executed by AI agents: research, code changes, QA passes, report writing. People assume this requires an orchestration framework. It requires a loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;step&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="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;MAX_STEPS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TOOLS&lt;/span&gt;&lt;span class="p"&gt;);&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// the model decided it is done&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TOOLS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;                     &lt;span class="c1"&gt;// no dynamic anything&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&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="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// sandboxed, permission-gated&lt;/span&gt;
  &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TOOL_BUDGET&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;    &lt;span class="c1"&gt;// clip. always.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;escalate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                          &lt;span class="c1"&gt;// never loop forever&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model decides &lt;em&gt;what&lt;/em&gt; to do next. The code owns &lt;em&gt;whether&lt;/em&gt; and &lt;em&gt;how long&lt;/em&gt;. That division is the entire design.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the loop taught us
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clip every tool result.&lt;/strong&gt; Ours are capped around 2,000 tokens; the full output goes to disk and the model gets the head, the tail and the path. Before the cap, a single verbose build log could eat an entire context window and lobotomize the rest of the session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Few orthogonal tools beat many clever ones.&lt;/strong&gt; Our agents carry six to nine tools with strict schemas. Every time we added a convenience tool that overlapped an existing one, tool-choice accuracy dropped. The model dithers exactly where a human would.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MAX_STEPS is a safety property, not a tuning knob.&lt;/strong&gt; When the budget runs out, the transcript escalates to a human with state intact. An agent that can loop forever eventually will, at 4 AM, against a rate-limited API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transcripts are JSONL, and JSONL is replayable.&lt;/strong&gt; Every step is appended to a log we can re-run against a new model or a fixed tool. Half of our debugging is replaying yesterday's transcript against today's code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reads are free, writes go through gates.&lt;/strong&gt; The loop can read anything in its sandbox. Deploys, deletions, payments and outbound messages leave the loop and enter the same mechanical gates our cron jobs use. We do not ask the model to remember to be careful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four model swaps later, the loop is byte-for-byte the same. Everything around it improved. That is what a good abstraction boundary feels like: the thing that changes fast is quarantined from the thing that must not.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>Structured output is a contract, not a request</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:18:59 +0000</pubDate>
      <link>https://dev.to/intframe/structured-output-is-a-contract-not-a-request-5cmb</link>
      <guid>https://dev.to/intframe/structured-output-is-a-contract-not-a-request-5cmb</guid>
      <description>&lt;p&gt;The first thing we standardized when model calls entered our pipelines was the boundary. Every call whose output feeds a machine returns JSON against a schema, and the schema is enforced by an ordinary validator. Not by asking nicely in the prompt. By rejecting the output and making the model try again with the validator's error in its face.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;feedback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&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="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;tries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&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;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RULES&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;feedback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;response_format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// constrained decoding where the API supports it&lt;/span&gt;
      &lt;span class="na"&gt;temperature&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="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// plain JSON Schema, same lib as our forms&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;errors&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="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;feedback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; Previous output failed validation: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;errors&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="s1"&gt;; &lt;/span&gt;&lt;span class="dl"&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="nf"&gt;quarantine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// a review queue. never a crash, never a guess.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three details carry most of the weight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enums beat free text.&lt;/strong&gt; The single biggest reliability jump came from replacing string fields with closed enums. One extraction pipeline went from a 4.1% rejection rate to 0.2% the week we enum-ified its category fields. A model that must choose from seven values cannot invent an eighth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feed the validator error back verbatim.&lt;/strong&gt; A bare "try again" retries the same mistake. The exact message, "items[2].price must be integer", fixes it on the next attempt almost every time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temperature 0 is necessary, not sufficient.&lt;/strong&gt; Greedy decoding removes randomness, not wrongness. The validator is still the one telling the truth.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quarantine is a feature
&lt;/h2&gt;

&lt;p&gt;The item that fails three attempts is the interesting one. It goes into a review queue, a human labels it, and the labeled case joins the regression suite. Our schemas have been hardened by two years of their own rejects. Which points at the real lesson: the schema is half of the prompt. Most of our prompt engineering time is spent deleting fields, tightening types and closing enums, because every degree of freedom you remove from the output is a hallucination that can no longer happen.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>One box is enough</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:18:57 +0000</pubDate>
      <link>https://dev.to/intframe/one-box-is-enough-5bf</link>
      <guid>https://dev.to/intframe/one-box-is-enough-5bf</guid>
      <description>&lt;p&gt;Everyone we meet assumes we run a fleet of cloud instances. We run one dedicated box in a Falkenstein data center, and it hosts every production site, database and bot we operate.&lt;/p&gt;

&lt;p&gt;This is not nostalgia. It is a forcing function. When everything lives on one machine, you cannot hide sloppy engineering behind autoscaling. A memory leak is a neighbor problem within the hour. A runaway cron shows up in everyone's latency. So the discipline has to live in three places, and all three are enforced by config, not by promises.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Every service gets a ceiling
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight systemd"&gt;&lt;code&gt;&lt;span class="c"&gt;# systemd override, applied to every unit, no exceptions&lt;/span&gt;
&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="nt"&gt;MemoryMax&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;512M
&lt;span class="nt"&gt;CPUQuota&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;80%
&lt;span class="nt"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;on-failure
&lt;span class="nt"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ceiling is not there to be hit. It is there so that when something does go wrong, the kernel kills one service instead of the OOM killer choosing a victim at random among forty.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Every scheduled job gets a lock, a timeout, and a heartbeat
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;*&lt;/span&gt;/5 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; flock &lt;span class="nt"&gt;-n&lt;/span&gt; /run/lock/sync.lock &lt;span class="nb"&gt;timeout &lt;/span&gt;240 /opt/jobs/sync.sh &lt;span class="se"&gt;\&lt;/span&gt;
            &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /opt/health/sync.ok
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lock stops a slow run from stacking behind itself. The timeout stops a hung run from holding the lock forever. The heartbeat file is the interesting one: a separate watchdog checks the mtime of every .ok file and treats staleness itself as the alarm. Jobs do not fail loudly in production. They fail by not running.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Routing is one file you can read
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;example.com {
  encode zstd gzip
  root * /srv/sites/example
  file_server
  handle /api/* { reverse_proxy 127.0.0.1:3160 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caddy terminates TLS for every domain and routes by hostname. Behind it, apps are containers or static directories. Databases are plain Postgres and MySQL, one instance per engine, many schemas. Nothing exotic, everything inspectable with standard tools.&lt;/p&gt;

&lt;p&gt;What one box buys you: latency you can reason about (every internal hop is a loopback), a single filesystem of truth (debugging is grep, not a distributed tracing seminar), and honest capacity planning (htop is the dashboard). The catch is that the box becomes precious, and precious things need paranoia. That paranoia, backups we actually restore, silence detection, blast-radius caps, is most of what the rest of these notes are about.&lt;/p&gt;

</description>
      <category>infra</category>
      <category>ops</category>
    </item>
    <item>
      <title>Design QA at 1:1</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Fri, 14 Aug 2026 18:15:44 +0000</pubDate>
      <link>https://dev.to/intframe/design-qa-at-11-12ba</link>
      <guid>https://dev.to/intframe/design-qa-at-11-12ba</guid>
      <description>&lt;p&gt;Two screens can look identical at a glance and be twenty percent apart in every measurement that matters. Font size, line height, spacing rhythm, contrast. The glance is the least reliable instrument in the building, and for months it was the only one we used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the original, not your memory of it
&lt;/h2&gt;

&lt;p&gt;When the brief is "make it feel like this reference", the first step is no longer a mood board. It is a probe run in both pages' consoles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;probe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sel&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getComputedStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sel&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="nx"&gt;sel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;letterSpacing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&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="s1"&gt;  &lt;/span&gt;&lt;span class="dl"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nav a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;probe&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="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it on the reference, run it on ours, diff the two tables. Design conversations change completely when both sides are looking at numbers. "It feels lighter" becomes "their h1 is 88/0.94 at weight 900, ours is 72/1.1 at 800", which is a ticket, not a debate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare crops, not impressions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Crop the same region from ours and theirs, stack them at 1:1, and look at the pair. Differences that vanish in side-by-side thumbnails become obvious.&lt;/li&gt;
&lt;li&gt;Do it on mobile first. Desktop hides crimes that a 390-pixel viewport confesses immediately.&lt;/li&gt;
&lt;li&gt;Automate the mechanical half: a script walks the page and flags text overflow, unexpected horizontal scroll and elements taller than their containers, before a human spends taste on it.&lt;/li&gt;
&lt;li&gt;Record scroll behavior too. Easing curves and pin durations are design decisions, and they are measurable in the performance timeline, not just feelable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Taste still decides
&lt;/h2&gt;

&lt;p&gt;None of this replaces judgment. It feeds judgment with facts. The measurements tell you where you diverge from the reference. Whether the divergence is a bug or a choice is still a human call, and making that call consciously is the entire difference between a site that is almost right and one that is right.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://intframe.com/blog/design-qa-at-one-to-one" rel="noopener noreferrer"&gt;INTFRAME engineering blog&lt;/a&gt;. The companion code lives at &lt;a href="https://github.com/intframe/scroll-qa" rel="noopener noreferrer"&gt;github.com/intframe/scroll-qa&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>testing</category>
      <category>css</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
