<?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: bestbee</title>
    <description>The latest articles on DEV Community by bestbee (@bestbee).</description>
    <link>https://dev.to/bestbee</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%2F4022115%2F1e3f6d82-8235-43d1-a5b6-ebda84a1f6b7.png</url>
      <title>DEV Community: bestbee</title>
      <link>https://dev.to/bestbee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bestbee"/>
    <language>en</language>
    <item>
      <title>A Free Agent Lab Needs an Explicit Blast-Radius Policy</title>
      <dc:creator>bestbee</dc:creator>
      <pubDate>Sun, 06 Sep 2026 20:20:16 +0000</pubDate>
      <link>https://dev.to/bestbee/a-free-agent-lab-needs-an-explicit-blast-radius-policy-46k7</link>
      <guid>https://dev.to/bestbee/a-free-agent-lab-needs-an-explicit-blast-radius-policy-46k7</guid>
      <description>&lt;p&gt;Picture a Monday standup I will treat as a composite, not a customer story. Three PRs landed. Same agent pattern. Same repo. The inference line did not move.&lt;/p&gt;

&lt;p&gt;Review still burned four senior hours. Why? The agent assumed a &lt;code&gt;.env&lt;/code&gt; layout that only existed on one laptop, then “fixed” tests by skipping the suite that would have caught it. Cheap compute did not fail. The environment story did.&lt;/p&gt;

&lt;p&gt;That is the decision I keep seeing platform leads get backwards. They ask whether a free coding path is cheaper than paid seats or a self-hosted box. The reversible question is narrower. &lt;strong&gt;Where do you want a wrong environment assumption to explode — and who owns the blast radius when it does?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The unit that reverses the call
&lt;/h2&gt;

&lt;p&gt;Tokens are a terrible proxy here. Server cost is a close second. An agent that can list files, read secrets-shaped env vars, and patch CI will spend your scarcest resource: review attention with production access.&lt;/p&gt;

&lt;p&gt;I use one working definition:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assumption tax&lt;/strong&gt; = (wrong environment claims per agent run) × (senior review hours to unwind one claim) × (blast radius 1–5)&lt;/p&gt;

&lt;p&gt;If that product stays below your gate, a shared free lab can be the right place to learn. If it does not, paying or self-hosting is not a prestige upgrade. It is an isolation purchase.&lt;/p&gt;

&lt;p&gt;This scorecard is a conversation tool, not objective truth. Change one variable and the keep-versus-exit call should be allowed to flip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where free, paid, and self-host actually fail
&lt;/h2&gt;

&lt;p&gt;I am not arguing that one path is “more AI.” I am mapping &lt;strong&gt;where the lie shows up&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;What the agent tends to assume&lt;/th&gt;
&lt;th&gt;Where the failure lands&lt;/th&gt;
&lt;th&gt;What you are really buying&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shared free models + shared free server&lt;/td&gt;
&lt;td&gt;“This box is my laptop.” Shared paths, shared caches, shared mystery env.&lt;/td&gt;
&lt;td&gt;Other people’s runs, plus the next intern who copies the prompt.&lt;/td&gt;
&lt;td&gt;Fast disagreement. Cheap enough to throw away.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paid vendor path&lt;/td&gt;
&lt;td&gt;“The vendor’s defaults match our org.” Hidden system prompt, managed tools, SaaS networking.&lt;/td&gt;
&lt;td&gt;Policy debt and a renewal argument.&lt;/td&gt;
&lt;td&gt;Support, SSO, and someone else’s uptime.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-host&lt;/td&gt;
&lt;td&gt;“Our cluster is as tidy as the README.” GPU queues, image drift, secret mounts.&lt;/td&gt;
&lt;td&gt;Platform on-call, not the original author.&lt;/td&gt;
&lt;td&gt;Control, and the ops load that comes with it.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;See the pattern? Free does not remove assumptions. It &lt;strong&gt;concentrates&lt;/strong&gt; them on a box nobody wants to admit is shared. Paid moves them into a contract. Self-host moves them into your pager.&lt;/p&gt;

&lt;p&gt;So the product question is not “is there a free server?” It is “is a disposable lab the right blast radius for this team this quarter?”&lt;/p&gt;

&lt;h2&gt;
  
  
  A lab is useful. A shadow platform is not.
&lt;/h2&gt;

&lt;p&gt;I want a lab when the team is still discovering which environment lies the agent tells. I do not want that lab to quietly become the way production code is written.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is one option with free model access and a free server path, which makes it a candidate for a &lt;strong&gt;time-boxed lab&lt;/strong&gt;, not a default production fabric. If you use it that way, run the same gates you would run on any other free path. Do not skip the owner field because the invoice is empty.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I log before anyone “just tries it”
&lt;/h3&gt;

&lt;p&gt;Label this as a proposed inventory, not a benchmark and not a claim about any vendor’s internals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# proposed: capture what an agent could assume on this box&lt;/span&gt;
&lt;span class="c"&gt;# do not paste real secrets into a ticket&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /tmp/assumption-lab &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; /tmp/assumption-lab

&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"cwd=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"user=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-un&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"os=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  git &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/src/app"&lt;/span&gt; rev-parse &lt;span class="nt"&gt;--show-toplevel&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"git=none"&lt;/span&gt;
  git &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/src/app"&lt;/span&gt; remote &lt;span class="nt"&gt;-v&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/:[^ ]*/:***/'&lt;/span&gt;
  &lt;span class="nb"&gt;env&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'/TOKEN|KEY|SECRET|PASSWORD|AWS|GH_/ {print $1"=redacted"}'&lt;/span&gt;
  &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/src/app"&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;head&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; env-assumptions.txt

&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; env-assumptions.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that file is long, the agent has a rich fantasy life. Rich is not free. It is a larger blast radius.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 4-gate assumption ledger
&lt;/h2&gt;

&lt;p&gt;Four fields. Fill them in writing. If a field is blank, you do not have a lab. You have an unnamed exception.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Isolation.&lt;/strong&gt; Can one agent run see another person’s files, caches, or env? If yes, blast radius starts at 4.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replay.&lt;/strong&gt; Can a reviewer reconstruct the exact context the agent saw, without SSHing into a snowflake box?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portability.&lt;/strong&gt; Can the same workflow move to paid or self-host without rewriting your policy from scratch?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incentive.&lt;/strong&gt; Does “it’s free” increase run volume faster than you can grow review capacity?&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Variables, named so they can reverse you
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;A&lt;/code&gt; = wrong environment claims per 10 agent-assisted PRs (count from review comments: missing tool, wrong path, skipped test, hallucinated secret).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;H&lt;/code&gt; = senior hours to unwind one claim (include the re-review, not just the first pass).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;B&lt;/code&gt; = blast radius, integer 1–5 (1 = author’s branch only; 5 = shared secrets or other teams’ jobs).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;C&lt;/code&gt; = review capacity this sprint, in senior hours you can actually spend.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;T&lt;/code&gt; = lab expiry date. A date, not “until it feels fine.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Keep the shared free lab only if&lt;/strong&gt; &lt;code&gt;A × H × B ≤ 0.3 × C&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;T&lt;/code&gt; is in the future &lt;strong&gt;and&lt;/strong&gt; Isolation is not a 5.&lt;/p&gt;

&lt;p&gt;That 0.3 is a starting conversation number. If your org already burns seniors on incident review, drop it to 0.15. If the lab is truly throwaway and cannot touch prod credentials, you can argue for 0.5. Write the number down so someone can attack it.&lt;/p&gt;

&lt;h3&gt;
  
  
  YAML you can paste into the ticket
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# proposed ledger — conversation tool, not a score&lt;/span&gt;
&lt;span class="na"&gt;lab&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shared_free&lt;/span&gt;   &lt;span class="c1"&gt;# shared_free | paid | self_host&lt;/span&gt;
  &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-em&lt;/span&gt;  &lt;span class="c1"&gt;# named human, not a DL&lt;/span&gt;
  &lt;span class="na"&gt;expiry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-10-05&lt;/span&gt;  &lt;span class="c1"&gt;# 28 days from a 2026-09-07 start; pick your own&lt;/span&gt;
  &lt;span class="na"&gt;exit_if_any&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;isolation_score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;assumption_tax_over_capacity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;replay_failed_twice&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;counts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# hypothetical worked row, not a measured study&lt;/span&gt;
  &lt;span class="na"&gt;A_wrong_claims_per_10_prs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;
  &lt;span class="na"&gt;H_hours_per_claim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.75&lt;/span&gt;
  &lt;span class="na"&gt;B_blast_radius&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;C_review_hours_this_sprint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;

&lt;span class="na"&gt;formula&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tax&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;6 * 0.75 * &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;    &lt;span class="c1"&gt;# 13.5&lt;/span&gt;
  &lt;span class="na"&gt;gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.3 * &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;       &lt;span class="c1"&gt;# 6.0&lt;/span&gt;
  &lt;span class="na"&gt;keep_shared_free&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In that filled row, the lab already lost. Inference can be zero. You still exit the shared path, or you shrink &lt;code&gt;B&lt;/code&gt; until the tax fits. Shrinking &lt;code&gt;B&lt;/code&gt; usually means: no shared credentials, no long-lived workspace, no “the intern’s job ran on the same disk.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Sensitivity: which lever actually flips the decision?
&lt;/h2&gt;

&lt;p&gt;Hold &lt;code&gt;H&lt;/code&gt; at 0.75 hours. That is 45 minutes of a person who can say no to a skip-test patch.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;A (claims / 10 PRs)&lt;/th&gt;
&lt;th&gt;B = 1 (isolated)&lt;/th&gt;
&lt;th&gt;B = 3 (shared lab)&lt;/th&gt;
&lt;th&gt;B = 5 (secrets-shaped)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1.5&lt;/td&gt;
&lt;td&gt;4.5&lt;/td&gt;
&lt;td&gt;7.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;9.0&lt;/td&gt;
&lt;td&gt;15.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;4.5&lt;/td&gt;
&lt;td&gt;13.5&lt;/td&gt;
&lt;td&gt;22.5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Against a 6.0 gate (&lt;code&gt;0.3 × 20&lt;/code&gt; hours), a tightly isolated lab with two messy claims still fits. The same claims on a shared box do not. Secrets-shaped blast radius never fits at these volumes.&lt;/p&gt;

&lt;p&gt;That is the comparison, stripped of mystique. &lt;strong&gt;Paid and self-host win when they cut &lt;code&gt;B&lt;/code&gt; or &lt;code&gt;A&lt;/code&gt;, not when they print a nicer model card.&lt;/strong&gt; If a paid seat still lets the agent assume your entire homedir, you bought a brand, not isolation. If self-host still mounts a kitchen-sink secret volume, you bought a GPU and kept the tax.&lt;/p&gt;

&lt;p&gt;Ask the ugly question in the review: &lt;em&gt;which single variable would make us keep the free lab?&lt;/em&gt; If the answer is “A drops below 2 and B stays at 1,” you have a testable pilot. If the answer is “we like that it is free,” you have an incentive problem, not a platform strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hard gates, owner, expiry, exit
&lt;/h2&gt;

&lt;p&gt;Write these as policy, not vibes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hard isolation gate.&lt;/strong&gt; Shared credentials, org-wide package caches with write access, or production kubeconfigs on the box → exit the shared free path the same day. No weekly meeting. Same day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replay gate.&lt;/strong&gt; Two PRs in a row where the reviewer cannot reconstruct the agent’s view → freeze new users until replay exists (log the prompt, the file tree hash, the redacted env keys).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portability gate.&lt;/strong&gt; If moving the workflow to paid or self-host requires a new data-handling story, the lab is lying about being a lab.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Owner.&lt;/strong&gt; A named engineering manager on the platform team. Rotating Slack channels do not own blast radius.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry.&lt;/strong&gt; 28 days, visible on a calendar. Renewal requires a new filled ledger, not a thumbs-up emoji.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exit.&lt;/strong&gt; Three documented options: (1) stop the shared lab, (2) shrink to isolated local-only free models, (3) buy isolation (paid or self-host) and keep the same assumption policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I like 28 days because it is long enough to see &lt;code&gt;A&lt;/code&gt; move and short enough that nobody builds a career on an unnamed exception.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tiny scorer (unexecuted example)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# proposed helper — not a measured benchmark
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assumption_tax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;keep_shared_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isolation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replay_fails&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;assumption_tax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;gate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isolation&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;isolation_hard_gate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;replay_fails&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replay_hard_gate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;expired&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tax &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tax&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &amp;gt; gate &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tax &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tax&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &amp;lt;= gate &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;gate&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;keep_shared_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;isolation&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="n"&gt;replay_fails&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="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-09-07&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expiry&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-10-05&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# expected: (False, 'tax 13.5 &amp;gt; gate 6.0')
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the function against &lt;strong&gt;your&lt;/strong&gt; last ten agent-assisted PRs. If you do not have ten, you do not have a keep decision. You have a sample size problem. Stay in lab mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this approach
&lt;/h2&gt;

&lt;p&gt;Skip the shared free server, even as a lab, if any of these are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You cannot name an owner who can revoke access the same day.&lt;/li&gt;
&lt;li&gt;The box can reach production data, customer exports, or long-lived cloud keys.&lt;/li&gt;
&lt;li&gt;Your reviewers cannot spend &lt;code&gt;H&lt;/code&gt; honestly — if seniors will rubber-stamp agent diffs, the tax formula is theater.&lt;/li&gt;
&lt;li&gt;You need a compliance story next week. A free shared environment is a discovery tool, not an audit artifact.&lt;/li&gt;
&lt;li&gt;You are trying to estimate production TCO from lab inference. You will undercount review, onboarding, and the cleanup of skipped tests. That estimate will reverse later, and you will not know why.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Individual makers on throwaway repos? Different sport. Isolate &lt;code&gt;B&lt;/code&gt; at 1 and most of this article is heavier than you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do this week
&lt;/h2&gt;

&lt;p&gt;Count &lt;code&gt;A&lt;/code&gt; on the last ten agent PRs. Redact env keys. Fill the YAML. Put &lt;code&gt;T&lt;/code&gt; on a calendar.&lt;/p&gt;

&lt;p&gt;If the tax misses the gate, do not “optimize prompts” as your first move. Cut blast radius. That usually means: dedicated workspace, no shared secrets, replay logs, and a written exit to paid or self-host that reuses the same policy.&lt;/p&gt;

&lt;p&gt;If you need a disposable place to pressure-test those gates — free model access, free server, same ledger — MonkeyCode can be that lab. Keep the expiry date anyway.&lt;/p&gt;

&lt;p&gt;Last question, and it is the only one I want in the thread: &lt;strong&gt;which variable — &lt;code&gt;A&lt;/code&gt;, &lt;code&gt;H&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt;, or &lt;code&gt;C&lt;/code&gt; — would have to move for you to keep a shared free agent path past 2026-10-05?&lt;/strong&gt; If you cannot name it, you are not choosing a lab. You are hoping the invoice stays quiet while the assumptions wander.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>Stay Free, Pay, or Self-Host AI Coding? Use This 6-Field Switch</title>
      <dc:creator>bestbee</dc:creator>
      <pubDate>Sat, 05 Sep 2026 19:00:20 +0000</pubDate>
      <link>https://dev.to/bestbee/stay-free-pay-or-self-host-ai-coding-use-this-6-field-switch-45ll</link>
      <guid>https://dev.to/bestbee/stay-free-pay-or-self-host-ai-coding-use-this-6-field-switch-45ll</guid>
      <description>&lt;p&gt;A platform lead dropped three printouts on the table and asked which one we should just pick. One was a free hosted path. One was a paid seat quote. One was a self-host estimate with a blank still sitting in the “who owns weekends” cell.&lt;/p&gt;

&lt;p&gt;I did not look at the prices first. I asked a ruder question. If this path dies on a Friday, who is on the hook—and how many days does it take to leave?&lt;/p&gt;

&lt;p&gt;Token price almost never reverses that answer. Exit days do. Data gravity does. On-call ownership does.&lt;/p&gt;

&lt;p&gt;This is a keep / buy / self-host switch for AI coding runtimes. It is a conversation tool, not a scoring gospel. If one variable below flips, the recommendation should flip with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fork is not unit price
&lt;/h2&gt;

&lt;p&gt;Free looks like a rounding error. Paid looks like a line item. Self-host looks like “we already have spare capacity somewhere.”&lt;/p&gt;

&lt;p&gt;None of those are the decision. The decision is whether you are buying &lt;em&gt;inference&lt;/em&gt;, &lt;em&gt;an SLA&lt;/em&gt;, or &lt;em&gt;a control plane&lt;/em&gt;. Mix those up and you will standardize the wrong path, then spend a quarter unpicking prompts, evals, and onboarding docs.&lt;/p&gt;

&lt;p&gt;I use six fields. Three of them can knock a column out. Fail a hard gate and that column is ineligible. You do not average your way back in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 6-field switch
&lt;/h2&gt;

&lt;p&gt;Score each field 1 (low pressure) to 5 (high pressure). Write the definition on the page. If two people cannot agree on the definition, you do not have a score. You have a vibe.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Data gravity (hard gate)
&lt;/h3&gt;

&lt;p&gt;Can prompts, repository slices, traces, and eval fixtures leave your network?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1:&lt;/strong&gt; public samples, no customer identifiers, no secrets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3:&lt;/strong&gt; internal service code, redacted logs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5:&lt;/strong&gt; production-adjacent source, customer data, regulated traces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hard gate:&lt;/strong&gt; if this field is 5, hosted free is out. Paid hosted is out unless legal already signed a data path. Remaining moves: self-host, or do not send the work to a model.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Failure owner (hard gate)
&lt;/h3&gt;

&lt;p&gt;Who gets paged when the path is down, slow, or confidently wrong?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1:&lt;/strong&gt; hobby use, no user-facing commitment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3:&lt;/strong&gt; platform will triage during business hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5:&lt;/strong&gt; an implicit availability promise and no named human&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hard gate:&lt;/strong&gt; if this field is 5 and you cannot name a person, &lt;em&gt;every&lt;/em&gt; column fails. Do not keep, buy, or self-host a path that has a crowd instead of an owner.&lt;/p&gt;

&lt;p&gt;Would you still call it “just a free trial” if the only pager is a Slack channel named &lt;code&gt;#ai-stuff&lt;/code&gt;?&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Review load
&lt;/h3&gt;

&lt;p&gt;Does cheaper generation increase review minutes faster than it saves write minutes?&lt;/p&gt;

&lt;p&gt;You do not need a perfect telemetry pipeline. You need a working definition: review minutes per merged change, plus rewind minutes when the change is wrong.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1:&lt;/strong&gt; boilerplate the team already reviews in seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3:&lt;/strong&gt; mixed; seniors are absorbing the extra diff&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5:&lt;/strong&gt; diffs grew, reverts grew, and nobody budgeted the cleanup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This field rarely knocks out a column by itself. It changes the &lt;em&gt;expiry&lt;/em&gt;. High review load means a short clock, not a bigger seat count.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Workflow coupling
&lt;/h3&gt;

&lt;p&gt;How many internal artifacts now assume this path exists? Count them. Literally.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt libraries and “how we use the tool” docs&lt;/li&gt;
&lt;li&gt;CI jobs that call the path&lt;/li&gt;
&lt;li&gt;Eval suites pinned to its behavior&lt;/li&gt;
&lt;li&gt;Onboarding checklists&lt;/li&gt;
&lt;li&gt;Permission grants and secret names&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then score it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1:&lt;/strong&gt; a handful of personal configs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3:&lt;/strong&gt; a team README and one CI experiment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5:&lt;/strong&gt; the path is in the default developer laptop image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;High coupling does not mean “pay more.” It means you do not get to pretend this is still a trial.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Control surface
&lt;/h3&gt;

&lt;p&gt;Do you need model pinning, network egress rules, an air-gap, or an audit trail you can produce without opening a vendor ticket?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1:&lt;/strong&gt; any decent model, any week, is fine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3:&lt;/strong&gt; you want logs and a stable system prompt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5:&lt;/strong&gt; you need to pin behavior, restrict egress, or keep weights and traces inside a boundary you operate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hard gate:&lt;/strong&gt; a 5 here knocks out hosted free. Paid hosted survives only if the contract actually gives you the controls. A marketing page that says “enterprise” is not a control surface.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Exit cost, in engineering days
&lt;/h3&gt;

&lt;p&gt;What actually moves when you switch—prompts, evals, CI, docs, permissions, and the muscle memory in onboarding?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1:&lt;/strong&gt; under 3 engineering days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3:&lt;/strong&gt; one to two weeks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5:&lt;/strong&gt; more than 15 engineering days, or nobody can even inventory the artifacts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If exit is a 5, a price of zero is a product decision you already made. You just did not put it on a P&amp;amp;L.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hard knockouts, before you add scores
&lt;/h2&gt;

&lt;p&gt;Do not average a knockout.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data gravity = 5&lt;/strong&gt; → hosted free ineligible. Paid hosted ineligible without a signed data path. Remaining options: self-host or refuse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure owner = 5 with no named human&lt;/strong&gt; → all options ineligible until you name someone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control surface = 5&lt;/strong&gt; → hosted free ineligible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exit cost = 5 and no archive rule&lt;/strong&gt; → you may &lt;em&gt;pilot&lt;/em&gt;. You may not &lt;em&gt;standardize&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything else is a weighted conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example (labeled hypothetical)
&lt;/h2&gt;

&lt;p&gt;I am not going to invent a customer. Here is a filled sheet for a composite 12-engineer backend group that a platform team supports. Treat the numbers as a rehearsal, not a benchmark.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data gravity&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Internal services, secret-scanning on, no customer PII in prompts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure owner&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Platform owns business-hours triage; no weekend page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review load&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Diffs are fatter; revert rate not measured yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow coupling&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;One README, no CI hook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control surface&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;No air-gap; pinning would be nice, not required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exit cost&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Personal prompt files, nothing in the laptop image&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read that left to right. Hosted free is still eligible. Paid is optional, not required. Self-host is optional, and probably a distraction, because nobody offered to own weekends.&lt;/p&gt;

&lt;p&gt;The line I would write on the page: &lt;strong&gt;keep a hosted free path as a time-boxed exception, 45-day expiry, named owner, no CI hook.&lt;/strong&gt; Re-score before anyone adds it to the developer image.&lt;/p&gt;

&lt;p&gt;Now flip one variable. Legal says customer source may appear in prompts next sprint. Data gravity becomes 5. The free hosted column dies on the hard gate. You do not “try it for one more month.” You stop, or you self-host inside the boundary, or you wait for a contract.&lt;/p&gt;

&lt;p&gt;That is the point of the switch. One field should be able to reverse you.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reproducible scoring artifact
&lt;/h2&gt;

&lt;p&gt;Copy this YAML, fill it in a PR, and refuse to discuss vendors until the PR has an owner and an expiry.&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="c1"&gt;# keep-buy-selfhost-switch.yml&lt;/span&gt;
&lt;span class="c1"&gt;# Conversation tool. Not objective truth.&lt;/span&gt;
&lt;span class="na"&gt;decision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;team&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backend-platform-composite"&lt;/span&gt;
  &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;platform-lead-name"&lt;/span&gt;
  &lt;span class="na"&gt;expires_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-10-21"&lt;/span&gt;  &lt;span class="c1"&gt;# 45 days from 2026-09-06&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draft"&lt;/span&gt;

&lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;data_gravity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;        &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;hard_gate_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;notes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PII&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;prompts"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;failure_owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;hard_gate_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;named_human&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;platform-lead-name"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;review_load&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;         &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;hard_gate_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;notes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;revert&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;measured"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;workflow_coupling&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;hard_gate_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;null&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;artifact_count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;control_surface&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;hard_gate_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;notes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;air-gap&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;requirement"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;exit_cost_days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;hard_gate_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;5&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;estimate_days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;2&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

&lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;hosted_free&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;eligible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hard-gate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;failures"&lt;/span&gt;
  &lt;span class="na"&gt;paid_hosted&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;eligible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;optional;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;SLA&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;demanded&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;yet"&lt;/span&gt;
  &lt;span class="na"&gt;self_host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;eligible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;optional;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;control-surface&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;5"&lt;/span&gt;

&lt;span class="na"&gt;recommendation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keep_hosted_free"&lt;/span&gt;
&lt;span class="na"&gt;exit_criteria&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data_gravity&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;5"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;artifact_count&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;5"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;expiry&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;date"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;revert&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;measured&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rising&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;two&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;weeks"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a tiny scorer you can run locally. It does not predict ROI. It only applies the knockouts and prints the remaining columns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Keep / buy / self-host switch. Conversation tool, not a verdict engine.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="n"&gt;HARD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sheet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data_gravity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;failure_owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;named_human&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;review_load&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;workflow_coupling&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;control_surface&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;exit_cost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;signed_data_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;contract_controls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sheet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_owner&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;HARD&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;named_human&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hosted_free&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paid_hosted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;self_host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;why&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no named owner — all columns ineligible&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;free_ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data_gravity&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;HARD&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;control_surface&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;HARD&lt;/span&gt;
    &lt;span class="n"&gt;paid_ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data_gravity&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;HARD&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signed_data_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;control_surface&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;HARD&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contract_controls&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self_ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;  &lt;span class="c1"&gt;# still requires an owner; staffing is a separate gate
&lt;/span&gt;    &lt;span class="n"&gt;why&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;free_ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;why&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hosted free knocked out by data gravity or control surface&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;paid_ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;why&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paid hosted knocked out until contract matches controls/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit_cost&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;HARD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;why&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exit is expensive — you may pilot, you may not standardize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hosted_free&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;free_ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paid_hosted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;paid_ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;self_host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self_ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;why&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;why&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no knockouts; compare coupling and review load&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sheet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Sheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;data_gravity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;failure_owner&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="n"&gt;named_human&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;platform-lead-name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;review_load&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="n"&gt;workflow_coupling&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;control_surface&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;exit_cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sheet&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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 keep_buy_selfhost.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see hosted free still eligible. Change &lt;code&gt;data_gravity=5&lt;/code&gt; and run it again. The free column should disappear. If it does not, your gate is theater.&lt;/p&gt;

&lt;p&gt;Want a second check? Set &lt;code&gt;named_human=""&lt;/code&gt; and &lt;code&gt;failure_owner=5&lt;/code&gt;. Every column should go false. That is the whole point of field 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a free model path actually belongs
&lt;/h2&gt;

&lt;p&gt;I only put a free hosted option in column A when I need a real alternative to “pay first” and “build a runtime first.” Otherwise the table collapses into a budget argument.&lt;/p&gt;

&lt;p&gt;Open-source tools are useful here because you can inspect the workflow, not because the invoice is zero. MonkeyCode is one such project: it offers free model access and a free server option, which is enough to populate the hosted-free column and score it against paid seats and a self-hosted runtime. Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;I am not going to invent model names, quotas, hardware, or benchmarks. Those claims go stale. The switch does not. If you cannot fill the six fields without marketing copy, you are not ready to standardize anyone's path—including this one.&lt;/p&gt;

&lt;p&gt;If you need a concrete free column to score, put MonkeyCode in column A, then run the knockouts before you write it into onboarding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Owner, expiry, exit
&lt;/h2&gt;

&lt;p&gt;Every filled sheet needs four lines at the bottom. Skip them and you have a slide, not a policy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Owner:&lt;/strong&gt; a named human who can stop the path, not a rotation alias.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry:&lt;/strong&gt; a calendar date. I use 45 days for a keep-free recommendation. On that date you re-score or you archive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Archive rule:&lt;/strong&gt; what happens to prompts, evals, and docs if you do not renew. “We'll remember” is not a rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exit criteria:&lt;/strong&gt; the variable that reverses you. Write it as a threshold, not a feeling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example exit line I actually want on the sheet: “If workflow coupling reaches 5, or data gravity reaches 5, we stop onboarding and either sign a contract, self-host, or unwind.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Sensitivity: what should reverse you?
&lt;/h2&gt;

&lt;p&gt;Ask this out loud. Do not ask “would we buy?”&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If data gravity went to 5 next month, would you still keep hosted free?&lt;/li&gt;
&lt;li&gt;If exit cost went to 15 days because CI grew around the path, would you still call it a trial?&lt;/li&gt;
&lt;li&gt;If the named owner went on leave, would the path still be allowed?&lt;/li&gt;
&lt;li&gt;If review load stayed at 5 for two measurement windows, would you pay, self-host, or stop?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If none of those would reverse you, you already chose, and the scorecard is decoration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this
&lt;/h2&gt;

&lt;p&gt;Skip this switch if you are a solo developer picking a personal tool. Six fields is ceremony.&lt;/p&gt;

&lt;p&gt;Skip it if security needs a real threat model, DPIA, or vendor review. A 1–5 score is not that review.&lt;/p&gt;

&lt;p&gt;Skip it if leadership already picked a vendor and wants a retrospective that looks like analysis. Garbage in, policy out.&lt;/p&gt;

&lt;p&gt;Skip self-host as a fantasy column if you will not staff the runtime. A control-surface 5 with a failure owner of “the platform team, I guess” is how you create a weekend outage with extra steps.&lt;/p&gt;

&lt;p&gt;And do not use the scorer as proof that free is cheaper. It cannot see review minutes, incident cost, or the political cost of unwinding a default tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do in the next working session
&lt;/h2&gt;

&lt;p&gt;Open the YAML. Fill it with one platform lead and one engineering manager in the room. Name the owner. Set the expiry. Run the Python knockouts live so nobody can average a 5.&lt;/p&gt;

&lt;p&gt;Then pick &lt;em&gt;one&lt;/em&gt; column. Keep hosted free, buy a contracted path, or self-host. Not two. A second path is a different decision, with a different cost of confusion, and it does not belong on this sheet.&lt;/p&gt;

&lt;p&gt;Which single field, if it moved one notch, would reverse your current choice?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Should a Platform Team Run Two AI Coding Paths? Use This 7-Field Fit Score</title>
      <dc:creator>bestbee</dc:creator>
      <pubDate>Fri, 04 Sep 2026 16:56:03 +0000</pubDate>
      <link>https://dev.to/bestbee/should-a-platform-team-run-two-ai-coding-paths-use-this-7-field-fit-score-1ooi</link>
      <guid>https://dev.to/bestbee/should-a-platform-team-run-two-ai-coding-paths-use-this-7-field-fit-score-1ooi</guid>
      <description>&lt;p&gt;Two AI coding paths. One invoice. Same two reviewers.&lt;/p&gt;

&lt;p&gt;I keep walking into planning rooms where an engineering manager has already split the stack: a free path for spikes, a paid or self-hosted path for the product monorepo. Nobody mapped repos to paths. Three weeks later the paid path looks unused, the free path is touching customer-adjacent services, and review time has quietly doubled. Finance still thinks the experiment is cheap. The calendar knows otherwise.&lt;/p&gt;

&lt;p&gt;Would you keep both paths after that meeting? I would not. Not without a written split, a leak number, and a date when the exception dies.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint that actually reverses the decision
&lt;/h2&gt;

&lt;p&gt;Token price will not save you here. Seat price will not either. The scarce resource is review hours colliding with data class.&lt;/p&gt;

&lt;p&gt;Free paths win on onboarding speed. Paid and self-hosted paths win on boundary control. Run both without rules and you do not get both benefits. You get the incentives of the easy path plus the blast radius of the hard one. Sound familiar?&lt;/p&gt;

&lt;p&gt;This article is a fit score for that split. It is a conversation tool, not objective truth. If one field flips, the recommendation should flip with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the seven fields before you argue tools
&lt;/h2&gt;

&lt;p&gt;Write the variables down. If a field has no owner, it is not a field. It is a vibe.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;data_class&lt;/strong&gt; — Highest sensitivity allowed on the free path. Public and disposable sandbox scores high. Restricted customer data scores zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;autonomy&lt;/strong&gt; — What the agent may do. Suggest-only or pull request with required review scores high. Direct writes to the default branch score zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;review_hours&lt;/strong&gt; — Slack in the review budget, in hours per week, after the current queue. No slack means the free path is a tax, not a gift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;onboarding_days&lt;/strong&gt; — Calendar days for a new hire to land a useful diff on the &lt;em&gt;paid or self-hosted&lt;/em&gt; path. If that path is a maze, people will leak back to the free one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;leak_pct&lt;/strong&gt; — Share of product-repo pull requests in the last 14 days that used the free path or a personal key. This is the field most teams refuse to measure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;switch_days&lt;/strong&gt; — Engineering days to enforce the split with policy plus CI, not a Slack announcement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;owner_expiry&lt;/strong&gt; — Named human plus a date no more than 60 days out. No owner, no expiry, score zero.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Score each field 0–3. I treat 0 as a hard stop on that dimension, not as a soft minus.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fit table, not a winner's podium
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Track&lt;/th&gt;
&lt;th&gt;Where it belongs&lt;/th&gt;
&lt;th&gt;What it is for&lt;/th&gt;
&lt;th&gt;Kill it when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A: free hosted models and free server&lt;/td&gt;
&lt;td&gt;Public, throwaway, or clearly internal sandbox repos&lt;/td&gt;
&lt;td&gt;Speed to first diff, architecture spikes, onboarding drills&lt;/td&gt;
&lt;td&gt;Restricted data appears, or product-repo leak_pct leaves 0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B: paid private tenant or self-hosted&lt;/td&gt;
&lt;td&gt;Product services, customer data, default-branch risk&lt;/td&gt;
&lt;td&gt;Bounded context, audit trail, stable policy&lt;/td&gt;
&lt;td&gt;Reviewers cannot finish the queue even after the split&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C: stop&lt;/td&gt;
&lt;td&gt;No named owner, no review slack, no data-class map&lt;/td&gt;
&lt;td&gt;You do not have an AI coding path. You have unreviewed text&lt;/td&gt;
&lt;td&gt;Any team that wants a third unofficial tool&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Track A is not "the cheap stack." Track B is not "the serious stack." They are different products serving different repos. If you cannot say which repo is on which track in one sentence, you are already on Track C and pretending otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: a 12-person team, labeled as an example
&lt;/h2&gt;

&lt;p&gt;This is a filled example for a conversation, not a customer report. Do not copy the numbers into a board deck as evidence.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;12 engineers, 3 staff reviewers&lt;/li&gt;
&lt;li&gt;Review budget: 15 hours / week after meetings&lt;/li&gt;
&lt;li&gt;4 sandbox repos that should be Track A&lt;/li&gt;
&lt;li&gt;2 product services with customer records that must be Track B&lt;/li&gt;
&lt;li&gt;Last 14 days: 18 extra sandbox diffs, about 20 minutes of review each → 6 hours&lt;/li&gt;
&lt;li&gt;Product-repo leak_pct: 12% still used the sandbox path&lt;/li&gt;
&lt;li&gt;Paid path onboarding_days: 9 (SSO plus repo allowlist plus a stale runbook)&lt;/li&gt;
&lt;li&gt;switch_days to add a classifier and CODEOWNERS: 4&lt;/li&gt;
&lt;li&gt;Owner: platform lead. Expiry: 45 days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scores I would put on the whiteboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data_class: &lt;strong&gt;1&lt;/strong&gt; (product leak means the free path is not actually sandbox-only)&lt;/li&gt;
&lt;li&gt;autonomy: &lt;strong&gt;2&lt;/strong&gt; (pull requests, but one bot can still open stacks of them)&lt;/li&gt;
&lt;li&gt;review_hours: &lt;strong&gt;1&lt;/strong&gt; (6 extra hours against a 15-hour budget is not fatal, but it is not slack)&lt;/li&gt;
&lt;li&gt;onboarding_days: &lt;strong&gt;1&lt;/strong&gt; (nine days is long enough for people to keep personal keys)&lt;/li&gt;
&lt;li&gt;leak_pct: &lt;strong&gt;0&lt;/strong&gt; (12% on a product repo is a gate fail, not a yellow flag)&lt;/li&gt;
&lt;li&gt;switch_days: &lt;strong&gt;2&lt;/strong&gt; (four days is doable)&lt;/li&gt;
&lt;li&gt;owner_expiry: &lt;strong&gt;3&lt;/strong&gt; (named owner, date inside 60 days)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total: 10, with two zeros. Recommendation: &lt;strong&gt;do not keep dual path this week&lt;/strong&gt;. Close Track A on anything that can see product data. Rebuild the sandbox allowlist. Measure leak_pct again in 14 days. Only then reopen Track A.&lt;/p&gt;

&lt;p&gt;If leak_pct had been 0% and data_class had been 3, the same team could keep both tracks. That is the point. One field reversed the split. The invoice did not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sensitivity: which threshold kills dual path?
&lt;/h2&gt;

&lt;p&gt;Hold every field constant and move one. That is the only sensitivity analysis this score needs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;leak_pct 0% → 10%:&lt;/strong&gt; dual path dies. You are laundering product work through the easy path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;review_hours 3 → 0:&lt;/strong&gt; dual path dies even with perfect data class. You are buying diffs you cannot read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;onboarding_days 3 → 0:&lt;/strong&gt; dual path dies later, not today. People will keep personal tools until Track B is day-one usable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;switch_days 3 → 0:&lt;/strong&gt; do not announce a split you cannot enforce. Announcements without CI become folklore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;data_class 3 → 0:&lt;/strong&gt; no debate. Restricted data on a free remote path is an exit, not a meeting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Break-even in review hours, still as an example: if Track A adds 6 review hours a week, and a fully loaded reviewer hour is an internal $150 planning number, that is $900 a week of attention. Compare that to whatever you actually pay for Track B. If Track B is more expensive in cash but cheaper in review contention &lt;em&gt;and&lt;/em&gt; it passes data_class, cash is not the scarce resource. Stop pretending it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  A scorer you can run this afternoon
&lt;/h2&gt;

&lt;p&gt;Label this as a checklist with arithmetic. It will not bless a vendor. It will stop a vague argument.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# dual_path_fit.py
# Conversation tool. Not a procurement oracle.
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Scores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;autonomy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;review_hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;onboarding_days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;leak_pct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;switch_days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;owner_expiry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;as_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;HARD_STOPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data_class&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;leak_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;autonomy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recommend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Scores&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;each field must be 0..3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;HARD_STOPS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;B_ONLY_OR_STOP: data_class, leak_pct, or autonomy is a hard stop. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do not keep a free path on product repos.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review_hours&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;owner_expiry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DUAL_PATH: Track A for public/sandbox only; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Track B for product. Recompute leak_pct in 14 days.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SINGLE_TRACK_B: paid or self-hosted only until onboarding and review slack recover.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP: no named owner, no slack, or no map. Do not scale a third tool.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Scores&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;data_class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;autonomy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;review_hours&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;onboarding_days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;leak_pct&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="n"&gt;switch_days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;owner_expiry&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_dict&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="nf"&gt;recommend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;example&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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 dual_path_fit.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a hard stop, not a pep talk. If your real scores print &lt;code&gt;DUAL_PATH&lt;/code&gt; while product repos still accept the free endpoint, the script is not wrong. Your inputs are.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforce the split in the repo, not in Slack
&lt;/h2&gt;

&lt;p&gt;A policy file is cheaper than another all-hands. Keep it boring.&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="c1"&gt;# ai-path-policy.yml&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-lead&lt;/span&gt;
&lt;span class="na"&gt;expires_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-10-20&lt;/span&gt;
&lt;span class="na"&gt;tracks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;A_free&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sandbox-*"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;spikes/*"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;data_class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;public&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;internal-sandbox&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;autonomy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull_request_required&lt;/span&gt;
  &lt;span class="na"&gt;B_paid_or_self_hosted&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repos&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;billing-service"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-api"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;data_class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;restricted&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;autonomy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull_request_required&lt;/span&gt;
&lt;span class="na"&gt;hard_gates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;no_default_branch_writes&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;product_repo_leak_pct_max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;recompute_every_days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then fail the build when a product repo still points at the free endpoint. This is a classifier, not a security novel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ci/check-ai-path.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="nv"&gt;REPO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GITHUB_REPOSITORY&lt;/span&gt;&lt;span class="p"&gt;##*/&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AI_ENDPOINT&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qx&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REPO&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;REPOS&lt;/span&gt;&lt;span class="sh"&gt;'
billing-service
customer-api
&lt;/span&gt;&lt;span class="no"&gt;REPOS
&lt;/span&gt;&lt;span class="k"&gt;then
  case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ENDPOINT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;*&lt;/span&gt;free&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;sandbox&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Track B repo cannot use the free path endpoint"&lt;/span&gt;
      &lt;span class="nb"&gt;exit &lt;/span&gt;1
      &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;esac&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is this elegant? No. Does it beat a wiki page that nobody reads? Yes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hard gates, owner, expiry, exit
&lt;/h2&gt;

&lt;p&gt;These are not scoring dimensions. They are stops.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hard gate:&lt;/strong&gt; restricted data never rides a free remote path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard gate:&lt;/strong&gt; no agent writes to the default branch on either track.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard gate:&lt;/strong&gt; if product-repo leak_pct is above 0% for two windows, freeze Track A.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Owner:&lt;/strong&gt; the platform lead, named in the YAML, not "the AI working group."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry:&lt;/strong&gt; 60 days or less. Dual path is an exception with a date, not a architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exit:&lt;/strong&gt; delete the free endpoint from product CI, revoke personal keys that touched Track B repos, and recompute onboarding_days before you reopen anything.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you cannot name the person who will kill Track A, you do not have a dual path. You have drift with a slide title.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a free hosted path still belongs
&lt;/h2&gt;

&lt;p&gt;Track A is real. It belongs on public sandboxes, throwaway spikes, and onboarding drills where the worst case is wasted review time, not a data-class incident.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is an open-source project with free model access and a free server option. That pair is one way to stand up Track A without pretending Track A is also Track B. I would still fill the seven fields before anyone points a product repo at it. I would not use a free remote path as a stealth tenant for customer data, and I would not treat "it booted" as an adoption win.&lt;/p&gt;

&lt;p&gt;If you want a sandbox to run this split against, use a free path only on repos that already pass data_class. Score leak_pct two weeks later. Keep the product work on paid or self-hosted iron.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this
&lt;/h2&gt;

&lt;p&gt;Skip this score if you already banned remote inference. You do not need a dual-path model. You need a self-hosted decision and a review budget.&lt;/p&gt;

&lt;p&gt;Skip it if there is no platform owner. A 7-field table in a team without an owner becomes fan fiction.&lt;/p&gt;

&lt;p&gt;Skip it if you are trying to bake off model quality. This artifact does not measure completion quality, latency, or token economics. Those are different papers, and they will lie to you if data_class is already a zero.&lt;/p&gt;

&lt;p&gt;Skip it in regulated workflows that need a vendor DPA, residency proof, or an audit trail you can hand to counsel. A fit score is not that packet.&lt;/p&gt;

&lt;p&gt;And skip a third unofficial tool while you argue this. Dual path is already one path too many when leak_pct is unknown.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question that matters
&lt;/h2&gt;

&lt;p&gt;Which field would reverse your split this month?&lt;/p&gt;

&lt;p&gt;If leak_pct flipped from 3 to 0, would you still defend Track A? If review_hours hit 0, would you still call the free path a developer-experience win? If onboarding_days on Track B stayed at nine, would you be honest that people will keep personal keys no matter what the wiki says?&lt;/p&gt;

&lt;p&gt;Write the seven numbers. Put an expiry on the YAML. Then ask the room to name the field that kills the split. If nobody can name it, you are not choosing a stack. You are hoping the reviewers stay quiet.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>career</category>
    </item>
    <item>
      <title>The 10M Token Trap: Free Quotas Change Developer Behavior Before They Change Your Costs</title>
      <dc:creator>bestbee</dc:creator>
      <pubDate>Fri, 04 Sep 2026 11:58:48 +0000</pubDate>
      <link>https://dev.to/bestbee/the-10m-token-trap-free-quotas-change-developer-behavior-before-they-change-your-costs-14d4</link>
      <guid>https://dev.to/bestbee/the-10m-token-trap-free-quotas-change-developer-behavior-before-they-change-your-costs-14d4</guid>
      <description>&lt;h1&gt;
  
  
  The 10M Token Trap: Free Quotas Change Developer Behavior Before They Change Your Costs
&lt;/h1&gt;

&lt;p&gt;Last sprint, I watched a senior engineer rewrite the same error-handling block four times. Each attempt used a longer prompt. Each prompt produced a slightly different version. None of them were worse than the original. None of them were better either.&lt;/p&gt;

&lt;p&gt;The tokens were free. So the thinking stopped.&lt;/p&gt;

&lt;p&gt;That's the trap nobody puts in the pricing table. A free quota doesn't just remove a cost. It removes a constraint — and constraints were doing more work than we admitted.&lt;/p&gt;

&lt;p&gt;This isn't a review of MonkeyCode. It's a warning about what happens after you plug it in. Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three behaviors free tokens unlock
&lt;/h2&gt;

&lt;p&gt;I've watched teams adopt free AI quotas three times now. The same three patterns appear every single time.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Prompt inflation
&lt;/h3&gt;

&lt;p&gt;When tokens cost money, you compress your prompt. You think about what the model actually needs. When tokens are free, you paste the entire file plus the error log plus the stack trace plus the ticket description.&lt;/p&gt;

&lt;p&gt;Longer prompts aren't better prompts. They're lazier ones. The model has to sift through irrelevant context to find the signal — and it will sometimes invent a signal where none exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Retry culture
&lt;/h3&gt;

&lt;p&gt;The first response is wrong. Previously, you'd think about why. Now you just click "regenerate" and hope.&lt;/p&gt;

&lt;p&gt;Four regenerations later, one of them happens to work. You merge it without understanding why it works. The next person who touches that code inherits a mystery.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Review outsourcing
&lt;/h3&gt;

&lt;p&gt;The most dangerous one. Free tokens make it easy to generate a diff, skim it, and approve it. The AI becomes a rubber stamp with a green checkmark.&lt;/p&gt;

&lt;p&gt;Code review isn't just about catching bugs. It's about building shared understanding. When the AI writes the code and the human just approves it, that understanding never forms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a governance problem, not a cost problem
&lt;/h2&gt;

&lt;p&gt;The standard advice is "set a budget." But a budget only caps the spend. It doesn't fix the behavior.&lt;/p&gt;

&lt;p&gt;The real fix is making token usage visible at the moment of decision — not in a monthly report, but right there in the editor.&lt;/p&gt;

&lt;p&gt;Here's a pattern that works: a lightweight usage log that records every AI interaction alongside the git commit it produced.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
token_trail.py — log AI interactions to a local SQLite DB.
Run this as a post-commit hook or a manual wrapper around your AI tool.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;DB_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.ai_usage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trail.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;DB_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DB_PATH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
CREATE TABLE IF NOT EXISTS usage (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp TEXT NOT NULL,
    prompt_chars INTEGER NOT NULL,
    response_chars INTEGER NOT NULL,
    regenerations INTEGER NOT NULL DEFAULT 1,
    commit_hash TEXT,
    file_path TEXT
)
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_interaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;regenerations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;commit_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT INTO usage (timestamp, prompt_chars, response_chars, regenerations, commit_hash, file_path) VALUES (?, ?, ?, ?, ?, ?)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;datetime&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="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;regenerations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;commit_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;file_path&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Pipe your prompt and response in as JSON: echo '{"prompt": "...", "response": "..."}' | python3 token_trail.py
&lt;/span&gt;    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;log_interaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;regenerations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;regenerations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;commit_hash&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;commit_hash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file_path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Logged &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; prompt chars to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DB_PATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't surveillance. It's a mirror. After two weeks, run this query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_chars&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_prompt_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regenerations&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_regens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;interactions&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;usage&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see the trend immediately. Prompt length creeping up. Regenerations climbing. That's the trap, quantified.&lt;/p&gt;

&lt;h2&gt;
  
  
  The weekly review ritual
&lt;/h2&gt;

&lt;p&gt;Once a week, spend fifteen minutes on the log. Not to punish anyone — to spot patterns.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt length up 30%?&lt;/strong&gt; Someone's pasting whole files instead of isolating the question. Talk about prompt structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regenerations up?&lt;/strong&gt; The model's output quality may have shifted, or the task is too vague. Either way, the fix is upstream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactions clustered in one file?&lt;/strong&gt; That file has a design problem. The AI is being used as a crutch for bad abstractions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ritual turns a free quota from a black box into a feedback loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about the free server?
&lt;/h2&gt;

&lt;p&gt;MonkeyCode's free server option has the same hidden dynamic. A shared server means shared queues. When the queue grows, developers don't wait — they retry. Retries make the queue worse. The server becomes a tragedy of the commons.&lt;/p&gt;

&lt;p&gt;The fix isn't a bigger server. It's a client-side backoff policy that makes the queue visible instead of hiding it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# queue_aware_request.sh — surface server queue time instead of hiding it&lt;/span&gt;
&lt;span class="c"&gt;# Usage: ./queue_aware_request.sh &amp;lt;endpoint&amp;gt; &amp;lt;key&amp;gt; &amp;lt;model&amp;gt; &amp;lt;prompt&amp;gt;&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;:?endpoint&lt;span class="p"&gt; required&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;:?key&lt;span class="p"&gt; required&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;3&lt;/span&gt;:?model&lt;span class="p"&gt; required&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;PROMPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;4&lt;/span&gt;:?prompt&lt;span class="p"&gt; required&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;START&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s.%N&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;HTTP_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/queue_response.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;model&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$MODEL&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;messages&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:[{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;role&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$PROMPT&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}],&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;max_tokens&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:256}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ENDPOINT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;END&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s.%N&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;ELAPSED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$END&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="nv"&gt;$START&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | bc&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HTTP_CODE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 200 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Non-200 response (&lt;/span&gt;&lt;span class="nv"&gt;$HTTP_CODE&lt;/span&gt;&lt;span class="s2"&gt;). Not retrying — investigate first."&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# If the request took &amp;gt;10s, log it. That's a queue signal, not a timeout.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ELAPSED&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt; 10"&lt;/span&gt; | bc &lt;span class="nt"&gt;-l&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"WARNING: &lt;/span&gt;&lt;span class="nv"&gt;$ELAPSED&lt;/span&gt;&lt;span class="s2"&gt; seconds. Queue is congested. Consider a different window."&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Completed in &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ELAPSED&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;s (HTTP &lt;/span&gt;&lt;span class="nv"&gt;$HTTP_CODE&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script doesn't prevent slow responses. It prevents blind retries. When developers see "queue congested" instead of a spinning cursor, they make different decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should ignore all of this
&lt;/h2&gt;

&lt;p&gt;If your team is three people building a weekend project, none of this matters. The trap only springs at scale.&lt;/p&gt;

&lt;p&gt;If you're a platform lead rolling this out to forty engineers, the governance pattern isn't optional. Free tokens without visibility is how you get a codebase written by nobody and reviewed by nobody.&lt;/p&gt;

&lt;p&gt;And if your team already has a healthy review culture — where people actually read diffs and ask questions — you can probably skip the log. The constraint you need is already there. It's called peer review.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable question
&lt;/h2&gt;

&lt;p&gt;Free quotas are a gift. But gifts change the receiver. The question isn't whether the tokens are really free.&lt;/p&gt;

&lt;p&gt;It's whether your team's behavior is still yours after you take them.&lt;/p&gt;

&lt;p&gt;If you're considering MonkeyCode's free tier, take the tokens. Take the server. But set up the log on day one, schedule the weekly review, and be honest about what the data shows. The tool is open source — the governance should be too.&lt;/p&gt;

&lt;p&gt;That's the one thing I'd insist on before any team I work with plugs in a free quota. Not because the tool is bad. Because free is a behavior modifier, and you want to see the modification happening in real time.&lt;/p&gt;

&lt;p&gt;MonkeyCode provides free models that can run this workflow.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>devtools</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Price Your AI Coding Stack With Cost per Merged PR, Not Token Price</title>
      <dc:creator>bestbee</dc:creator>
      <pubDate>Fri, 04 Sep 2026 10:58:38 +0000</pubDate>
      <link>https://dev.to/bestbee/price-your-ai-coding-stack-with-cost-per-merged-pr-not-token-price-4mci</link>
      <guid>https://dev.to/bestbee/price-your-ai-coding-stack-with-cost-per-merged-pr-not-token-price-4mci</guid>
      <description>&lt;p&gt;Last month a founder told me their team had zero AI spend. They were proud of it. So I asked one question: what happened to your pull request cycle time?&lt;/p&gt;

&lt;p&gt;Silence.&lt;/p&gt;

&lt;p&gt;A zero on the invoice is not a win. It is a cost that moved somewhere else — usually to the calendar. If you are choosing between a free hosted AI coding stack, a self-hosted model, and a paid API, token price is the wrong unit. The right unit is cost per completed task: the PR that merges, the test that passes, the refactor that ships.&lt;/p&gt;

&lt;p&gt;MonkeyCode, an open-source AI coding assistant, currently offers a free tier with 10 million tokens and a free hosted server. That is exactly the kind of offer that makes teams stop doing the math. I would still run the math — because "free" is a price, not a strategy.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap of the zero-price line
&lt;/h2&gt;

&lt;p&gt;Ten million tokens sounds like a lot. It is a lot — as an input. But tokens are not outcomes. A single mid-size PR review, with context loading, diff analysis, and one fix loop, can consume a meaningful slice of that budget. Let me show the arithmetic with a planning assumption: 200,000 tokens per completed review-and-fix cycle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000,000 ÷ 200,000 = 50 completed cycles per month
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fifty PRs a month is a real workload for a small team. But that number assumes zero waste. Retries, truncated contexts, re-reading the repository, and regenerating a failed diff all burn tokens without producing a merged PR. The moment your team starts fighting the context window, the real cost per task climbs — even though the invoice stays at zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the unit first
&lt;/h2&gt;

&lt;p&gt;Here is the model I use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C_task = (T + I + F) / N
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;T&lt;/code&gt; = token spend&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;I&lt;/code&gt; = infrastructure spend&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;F&lt;/code&gt; = engineering time spent on retries, failures, and workarounds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;N&lt;/code&gt; = completed tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The free tier sets T and I to zero. That is the trap: F is the variable that decides everything, and it is the one nobody tracks.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example, with honest numbers
&lt;/h2&gt;

&lt;p&gt;Let me make the assumptions explicit. This is a planning model, not a benchmark — replace every number with your own measurements.&lt;/p&gt;

&lt;p&gt;Team: 5 engineers. Output: 20 merged PRs per month. Burdened engineering cost: $60 per hour.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Token spend&lt;/th&gt;
&lt;th&gt;Infra spend&lt;/th&gt;
&lt;th&gt;Hidden engineering hours&lt;/th&gt;
&lt;th&gt;Monthly cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free hosted&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;12 h (retries, context resets)&lt;/td&gt;
&lt;td&gt;$720&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-hosted&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$150 amortized hardware&lt;/td&gt;
&lt;td&gt;16 h (ops, updates, debugging)&lt;/td&gt;
&lt;td&gt;$1,110&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paid API&lt;/td&gt;
&lt;td&gt;$200&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;4 h&lt;/td&gt;
&lt;td&gt;$440&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In this example, the paid API wins. That is the result you need to be willing to see: free is not automatically cheapest, and self-hosting is not automatically cheaper than renting.&lt;/p&gt;

&lt;p&gt;Now run the sensitivity. If the free tier costs only 4 hidden hours per month, it drops to $240 and beats the paid API. If your burdened rate is $30 per hour instead of $60, the free tier's 12 hidden hours cost $360 — still competitive. The break-even question is simple: how many engineering hours is the free tier burning per month?&lt;/p&gt;

&lt;p&gt;That is the number to measure in a pilot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fit criteria: who should pick which
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Free hosted&lt;/th&gt;
&lt;th&gt;Self-hosted&lt;/th&gt;
&lt;th&gt;Paid API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exploratory prototyping&lt;/td&gt;
&lt;td&gt;Best fit&lt;/td&gt;
&lt;td&gt;Overkill&lt;/td&gt;
&lt;td&gt;Fine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI automation with latency SLOs&lt;/td&gt;
&lt;td&gt;Risky — shared server&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Best fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production codegen at scale&lt;/td&gt;
&lt;td&gt;Watch the hidden hours&lt;/td&gt;
&lt;td&gt;Good if you have ops&lt;/td&gt;
&lt;td&gt;Predictable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data-sensitive or regulated&lt;/td&gt;
&lt;td&gt;Fails compliance&lt;/td&gt;
&lt;td&gt;Best fit&lt;/td&gt;
&lt;td&gt;Depends on terms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost-constrained internal tools&lt;/td&gt;
&lt;td&gt;Best fit with exit triggers&lt;/td&gt;
&lt;td&gt;Too expensive&lt;/td&gt;
&lt;td&gt;Watch the invoice&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Five questions before you commit
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Can you name your completed-task volume per week? If not, you cannot do this math.&lt;/li&gt;
&lt;li&gt;What is your burdened engineering hour cost? Use the real number, not the salary line.&lt;/li&gt;
&lt;li&gt;How many hours per week do you lose to retries, rate limits, and context resets today? Measure it for two weeks before you decide.&lt;/li&gt;
&lt;li&gt;Does data leave your boundary when you use a hosted free server? If the answer is "I don't know", that is a no.&lt;/li&gt;
&lt;li&gt;Who owns the self-hosted stack when it breaks at 2 AM? If the answer is "nobody", self-hosting is not free.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Limitations of this approach
&lt;/h2&gt;

&lt;p&gt;This model ignores what it cannot price: latency variance, trust, vendor lock-in, and the risk that free quotas change without notice. Free tiers are marketing instruments with an expiry date, even when none is printed. Verify the current terms before you plan around them.&lt;/p&gt;

&lt;p&gt;Who should not use this framework: teams with compliance constraints, latency-sensitive CI pipelines, or no one willing to track the hidden hours. If you cannot measure F, you are choosing by vibes — and the invoice will find you later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two-week test
&lt;/h2&gt;

&lt;p&gt;The cheapest way to test this model is to run a two-week pilot on a free hosted tier. MonkeyCode currently offers 10 million tokens and a free server, which is enough runway to measure your own C_task. Track the number, not the invoice. If the hidden hours exceed the paid alternative, you have your answer. If they don't, you just found a free server worth keeping.&lt;/p&gt;

&lt;p&gt;MonkeyCode provides free models that can run this workflow.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Should a Platform Team Bless a Free AI Coding Path? Use This 5-Gate Exception</title>
      <dc:creator>bestbee</dc:creator>
      <pubDate>Thu, 03 Sep 2026 14:23:26 +0000</pubDate>
      <link>https://dev.to/bestbee/should-a-platform-team-bless-a-free-ai-coding-path-use-this-5-gate-exception-53e7</link>
      <guid>https://dev.to/bestbee/should-a-platform-team-bless-a-free-ai-coding-path-use-this-5-gate-exception-53e7</guid>
      <description>&lt;p&gt;The slide had three boxes. Free hosted. Self-hosted. Paid contract. Nobody had written an expiry date under any of them.&lt;/p&gt;

&lt;p&gt;Which box is cheaper when the invoice is zero and the review queue is not? That is the question I want a platform lead to answer before the Slack poll starts. Cash is the loud variable. It is rarely the reversing one.&lt;/p&gt;

&lt;p&gt;I keep watching 12- to 20-person platform groups treat a free AI coding path as a harmless default. It feels kind. It also quietly rewrites who owns incidents, where source leaves the building, and how long a “pilot” is allowed to live. The decision is not buy versus build. It is whether you will bless a time-boxed exception, or accidentally install a second toolchain with no owner.&lt;/p&gt;

&lt;p&gt;This article is a policy gate, not a product review. The scorecard is a conversation tool. It is not objective truth. If one variable below flips, the whole answer can flip with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint that reverses the room
&lt;/h2&gt;

&lt;p&gt;Label this a worked example, not a benchmark. Three seniors, extra review on AI-authored diffs, 3.5 hours a week each, loaded cost $175 an hour. That is $1,837.50 a week. Call it $7,350 a month of senior time before anyone pays a seat.&lt;/p&gt;

&lt;p&gt;A free path that burns that much review is not free. A paid path that cuts review in half can win on labor even when the invoice looks worse. A self-hosted path that keeps restricted source inside the boundary can win even when both cash and latency look worse. Ask it out loud: if review hours stay high, do you still want the zero-invoice box?&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop pricing the path in tokens
&lt;/h2&gt;

&lt;p&gt;Tokens are a vendor proxy. Your P&amp;amp;L does not close on tokens. It closes on review hours, incident ownership, data-class breaches, and the cost of leaving.&lt;/p&gt;

&lt;p&gt;Define the units before you score anything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;R&lt;/code&gt; — extra senior review hours per week caused by AI-authored changes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;L&lt;/code&gt; — loaded hourly cost of those seniors&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;D&lt;/code&gt; — data class of the repos in scope (&lt;code&gt;public&lt;/code&gt;, &lt;code&gt;internal&lt;/code&gt;, &lt;code&gt;restricted&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;W&lt;/code&gt; — share of AI output that lands in the normal PR path with tests and CODEOWNERS (0 to 1)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;X&lt;/code&gt; — engineer-days required to exit the path (prompts, evals, habit, docs)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;T&lt;/code&gt; — calendar days until the exception expires&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Monthly labor drag is &lt;code&gt;R * L * 4.3&lt;/code&gt;. If that number exceeds the paid-seat or self-host ops line &lt;em&gt;and&lt;/em&gt; &lt;code&gt;D&lt;/code&gt; is not restricted, money can decide. If &lt;code&gt;D&lt;/code&gt; is restricted, money does not get a vote.&lt;/p&gt;

&lt;p&gt;Why these six? Because I have seen teams “save” a few hundred dollars a month and spend a senior-week cleaning a chat-only workflow that never became a PR. Cheap tokens. Expensive process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a free path actually belongs
&lt;/h2&gt;

&lt;p&gt;A free hosted coding path is useful when you need a 14- to 45-day exception: no PO, no hardware ticket, enough signal to accept, buy, self-host, or kill. It is a substrate for the gate. It is not the conclusion.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is an open-source project with free model access and a free server option. I am using those two availability claims as a way to run the exception below, not as proof that the free box should win.&lt;/p&gt;

&lt;p&gt;If your data class or review budget already fails Gate 1 or Gate 2, skip the free hosted option. Do not “just try it” on restricted source because the invoice is zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 5-gate exception
&lt;/h2&gt;

&lt;p&gt;Run the gates in order. A hard fail stops the process. Scores are for conversation, not for averaging your way into a yes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gate 1 — Data class and tenancy (hard)
&lt;/h3&gt;

&lt;p&gt;Can snippets, comments, secrets, and proprietary names leave your boundary?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pass: repos are &lt;code&gt;public&lt;/code&gt; or &lt;code&gt;internal&lt;/code&gt;, and you have a written rule for what may be pasted.&lt;/li&gt;
&lt;li&gt;Fail: &lt;code&gt;restricted&lt;/code&gt; source, secrets in prompts, or “we will be careful” with no owner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Free hosted fails closed on restricted source. Self-host or a paid contract with a real data-processing term is the remaining pair. Do not negotiate this gate with a cost model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gate 2 — Review-hour budget (hard if &lt;code&gt;R&lt;/code&gt; has no owner)
&lt;/h3&gt;

&lt;p&gt;Who absorbs &lt;code&gt;R&lt;/code&gt;? If the answer is “the seniors will figure it out,” you already failed.&lt;/p&gt;

&lt;p&gt;Write the budget on one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;R_budget = 2.0 hours/week/senior
R_actual = measured over 10 merged AI-touched PRs
owner    = staff engineer on the platform team
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;R_actual&lt;/code&gt; exceeds &lt;code&gt;R_budget&lt;/code&gt; for two consecutive weeks, the exception ends. Not “we should look at it.” Ends.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gate 3 — Workflow match
&lt;/h3&gt;

&lt;p&gt;Does the output die in a side-channel, or does it hit the same PR template, test gate, and CODEOWNERS file as human work?&lt;/p&gt;

&lt;p&gt;Measure &lt;code&gt;W&lt;/code&gt; on the last 20 AI-touched changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;W &amp;gt;= 0.8&lt;/code&gt; — the path is inside the workflow&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0.5 &amp;lt;= W &amp;lt; 0.8&lt;/code&gt; — fix the template before you expand seats&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;W &amp;lt; 0.5&lt;/code&gt; — you are funding a shadow editor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A free path that cannot survive your existing merge rules is not an adoption win. It is a bypass.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gate 4 — Incentive alignment
&lt;/h3&gt;

&lt;p&gt;Who gets paged when the generated change fails at 2 a.m.?&lt;/p&gt;

&lt;p&gt;If the person who accepted the diff is not on the incident rotation, the free path is paying people to merge lines they will not own. That incentive will beat any scoring rubric. I would rather have a slower paid tool with a named on-call than a fast free tool that orphans failures.&lt;/p&gt;

&lt;p&gt;Ask the room: would you still merge this if you were on call tonight?&lt;/p&gt;

&lt;h3&gt;
  
  
  Gate 5 — Exit cost inside &lt;code&gt;T&lt;/code&gt; days
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;T&lt;/code&gt; is 21 days unless you write a shorter number. &lt;code&gt;X&lt;/code&gt; must be estimated on day 0, not discovered on day 20.&lt;/p&gt;

&lt;p&gt;Count engineer-days to leave:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prompt and rule files that only work on this path&lt;/li&gt;
&lt;li&gt;Eval cases that are not in git&lt;/li&gt;
&lt;li&gt;Human habit (the “just paste it here” channel)&lt;/li&gt;
&lt;li&gt;Docs that name the path as default&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If &lt;code&gt;X &amp;gt; 3&lt;/code&gt; engineer-days, you are not piloting. You are migrating without a decision. Shrink the surface until &lt;code&gt;X&lt;/code&gt; fits inside &lt;code&gt;T&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example (hypothetical 15-person platform team)
&lt;/h2&gt;

&lt;p&gt;Do not treat these numbers as measurements. They are a filled sheet so you can argue with the cells.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Free hosted exception&lt;/th&gt;
&lt;th&gt;Paid contract&lt;/th&gt;
&lt;th&gt;Self-hosted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;D&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;internal services only&lt;/td&gt;
&lt;td&gt;internal + one restricted repo via DPA&lt;/td&gt;
&lt;td&gt;restricted monorepo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;R&lt;/code&gt; hours/week total&lt;/td&gt;
&lt;td&gt;10.5 (3 seniors × 3.5)&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;
&lt;td&gt;8.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;L&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$175&lt;/td&gt;
&lt;td&gt;$175&lt;/td&gt;
&lt;td&gt;$175&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly labor (&lt;code&gt;R * L * 4.3&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;$7,913&lt;/td&gt;
&lt;td&gt;$4,515&lt;/td&gt;
&lt;td&gt;$6,020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cash outlay / month&lt;/td&gt;
&lt;td&gt;$0 invoice&lt;/td&gt;
&lt;td&gt;$1,200 seats (illustrative)&lt;/td&gt;
&lt;td&gt;$900 ops (illustrative)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fully loaded month&lt;/td&gt;
&lt;td&gt;$7,913&lt;/td&gt;
&lt;td&gt;$5,715&lt;/td&gt;
&lt;td&gt;$6,920&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;W&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.45 until PR template exists&lt;/td&gt;
&lt;td&gt;0.85&lt;/td&gt;
&lt;td&gt;0.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;X&lt;/code&gt; engineer-days&lt;/td&gt;
&lt;td&gt;1.5 if &lt;code&gt;T=21&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;4.0&lt;/td&gt;
&lt;td&gt;6.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;T&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;21 days&lt;/td&gt;
&lt;td&gt;12-month term&lt;/td&gt;
&lt;td&gt;no expiry unless you write one&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On this sheet the free path loses on labor &lt;em&gt;and&lt;/em&gt; on &lt;code&gt;W&lt;/code&gt;. Paid wins if &lt;code&gt;D&lt;/code&gt; allows it. Self-host wins the moment you move the restricted monorepo into scope, because Gate 1 eliminates the other two.&lt;/p&gt;

&lt;p&gt;Change one cell. If those three seniors get &lt;code&gt;R&lt;/code&gt; down to 1.0 hour a week each, free hosted labor drops to about $2,258 a month. Then free can beat paid on money — still only if &lt;code&gt;W&lt;/code&gt; climbs above 0.8 and &lt;code&gt;D&lt;/code&gt; stays internal. That is the point of the sheet. Find the cell that reverses you.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tiny policy file, not a framework poster
&lt;/h2&gt;

&lt;p&gt;Keep the exception in git so it cannot hide in a doc nobody opens. Proposal only; this is not production policy-as-code.&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="c1"&gt;# exception-ai-coding-path.yaml&lt;/span&gt;
&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ai-coding-free-path-2026-09&lt;/span&gt;
&lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;platform-lead&lt;/span&gt;
&lt;span class="na"&gt;expires_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2026-09-24&lt;/span&gt;
&lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;data_class_allowed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;public&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;repos_glob&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;services/*"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools/*"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;repos_denied&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;monorepo-restricted/*"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;budget&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extra_review_hours_per_senior_per_week&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.0&lt;/span&gt;
  &lt;span class="na"&gt;senior_count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;loaded_hourly_usd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;175&lt;/span&gt;
&lt;span class="na"&gt;gates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;min_workflow_match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.8&lt;/span&gt;
  &lt;span class="na"&gt;max_exit_engineer_days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;oncall_must_own_merge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;exit_criteria&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;keep&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;W&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0.8&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AND&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;R_actual&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;budget&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;weeks&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AND&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;D&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;unchanged"&lt;/span&gt;
  &lt;span class="na"&gt;buy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;labor+cash&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;paid&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;free&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;labor&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AND&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;D&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;still&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;allowed"&lt;/span&gt;
  &lt;span class="na"&gt;self_host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;any&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scope&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;becomes&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;restricted"&lt;/span&gt;
  &lt;span class="na"&gt;kill&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;any&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hard-gate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fail&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;OR&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;owner&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;missing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;OR&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;expiry&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hit"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A conversation calculator you can run locally. Proposal, unexecuted, numbers are yours to replace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# proposal: exception_score.py — conversation tool, not a verdict
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;data_class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;          &lt;span class="c1"&gt;# public | internal | restricted
&lt;/span&gt;    &lt;span class="n"&gt;r_hours_per_week&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;loaded_hourly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;cash_per_month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;workflow_match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;    &lt;span class="c1"&gt;# 0-1
&lt;/span&gt;    &lt;span class="n"&gt;exit_days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;oncall_owns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;days_until_expiry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;monthly_labor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;r_hours_per_week&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loaded_hourly&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;4.3&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hard_fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data_class&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;restricted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;free-hosted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gate 1: restricted source cannot ride a free hosted path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oncall_owns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gate 4: merger is not on call&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;days_until_expiry&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gate 5: exception expired&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit_days&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;free-hosted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Gate 5: exit cost too high for a pilot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fully_loaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&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;monthly_labor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cash_per_month&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hard_fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STOP — &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;workflow_match&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HOLD — fix PR template before expanding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CONTINUE — loaded $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;fully_loaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/mo until expiry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;free&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;free-hosted-exception&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;internal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;10.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;175&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="mf"&gt;0.45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;paid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paid-contract&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;internal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;self-hosted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;restricted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;175&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;paid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;decide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;labor=$&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;monthly_labor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the hypothetical inputs this prints a STOP or HOLD on the free path because &lt;code&gt;W&lt;/code&gt; is 0.45, and it keeps self-host alive for restricted source. Change &lt;code&gt;workflow_match&lt;/code&gt; to 0.85 and &lt;code&gt;r_hours_per_week&lt;/code&gt; to 3.0. Does your call reverse? If it does not, Gate 1 or Gate 4 is doing the real work, and you can stop arguing about invoices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inventory the shadow path before you bless a new one
&lt;/h2&gt;

&lt;p&gt;You cannot approve an exception if you do not know what is already in use. Proposal commands only; run them on your own machine, against your own repos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# proposal: find existing AI-touch markers in git history (last 30 days)&lt;/span&gt;
git log &lt;span class="nt"&gt;--since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'30 days ago'&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format:&lt;span class="s1"&gt;'%h %s'&lt;/span&gt; | rg &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'copilot|codex|llm|generated by|ai-assisted'&lt;/span&gt;

&lt;span class="c"&gt;# proposal: see whether CODEOWNERS will even see the path&lt;/span&gt;
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; CODEOWNERS &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'CODEOWNERS present'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'NO CODEOWNERS — Gate 3 fail'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If those come back noisy, you already have a free path. The 5-gate file is how you pull it out of Slack and into an expiry date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hard gates, owner, expiry, exit
&lt;/h2&gt;

&lt;p&gt;Write these on the same page as the scorecard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hard gates:&lt;/strong&gt; restricted source on a free hosted path; secrets in prompts; no CODEOWNERS; merger not on call; missing owner; missing expiry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Owner:&lt;/strong&gt; one platform lead, named, not a rotation alias.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry:&lt;/strong&gt; 21 days from the first merged AI-touched PR, not from the meeting where someone said “sure.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exit:&lt;/strong&gt; &lt;code&gt;keep&lt;/code&gt;, &lt;code&gt;buy&lt;/code&gt;, &lt;code&gt;self_host&lt;/code&gt;, or &lt;code&gt;kill&lt;/code&gt;. No fourth option called “leave it for now.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If expiry hits with no written call, the default is &lt;code&gt;kill&lt;/code&gt;. Silence is not consent to keep a toolchain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Teams whose source is regulated or customer-secret. Gate 1 is the article. Stop there.&lt;/li&gt;
&lt;li&gt;Teams with no senior review slack. You cannot budget &lt;code&gt;R&lt;/code&gt; at zero and hope quality appears.&lt;/li&gt;
&lt;li&gt;Individuals looking for an editor tutorial. This is a policy exception for a team that already has a merge path.&lt;/li&gt;
&lt;li&gt;Groups that will not name an owner or an end date. The gates will not save you from a permanent “temporary” tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;I did not measure your review hours. I did not benchmark any model, queue, or box. Loaded cost varies by city and level; $175 is a placeholder so the multiplication is visible. &lt;code&gt;W&lt;/code&gt; needs a definition of “AI-touched” that your team will actually tag. If you refuse to tag, the scorecard is theater.&lt;/p&gt;

&lt;p&gt;Open-source free paths also move. Treat availability as an input that can disappear, which is another reason &lt;code&gt;T&lt;/code&gt; stays short. Do not build a default workflow on an option you have not promised to fund.&lt;/p&gt;

&lt;h2&gt;
  
  
  What would reverse you?
&lt;/h2&gt;

&lt;p&gt;Not “would you try the free box?” That question invites a shrug. Ask which cell reverses the call.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;D&lt;/code&gt; becomes restricted, do you self-host the same week? If &lt;code&gt;R&lt;/code&gt; falls under 2 hours a week per senior, does free hosted suddenly win? If &lt;code&gt;X&lt;/code&gt; is 5 engineer-days, will you still pretend this is a pilot?&lt;/p&gt;

&lt;p&gt;Write the reversing cell in the YAML. Then the Tuesday slide can keep its three boxes. Only one of them gets an expiry date, an owner, and a kill switch. That is the whole job.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>management</category>
      <category>opensource</category>
    </item>
    <item>
      <title>AI Code Is Cheap. Its Cleanup Isn't. Run a 90-Day Churn Gate</title>
      <dc:creator>bestbee</dc:creator>
      <pubDate>Wed, 02 Sep 2026 13:29:36 +0000</pubDate>
      <link>https://dev.to/bestbee/ai-code-is-cheap-its-cleanup-isnt-run-a-90-day-churn-gate-5ecb</link>
      <guid>https://dev.to/bestbee/ai-code-is-cheap-its-cleanup-isnt-run-a-90-day-churn-gate-5ecb</guid>
      <description>&lt;p&gt;The dashboard looked great. Pull requests per week had doubled. The platform lead who shared it wasn't celebrating, though — he opened a second chart first. Files re-touched within two weeks of merge were climbing just as fast.&lt;/p&gt;

&lt;p&gt;I keep seeing that pattern in adoption reviews. Teams measure what the AI produces: lines, PRs, test coverage. Almost nobody measures what the team pays afterward. That's the gap this workflow closes.&lt;/p&gt;

&lt;p&gt;Here's a 90-day pilot design — a fixed token budget, a churn measurement script, and hard go/stay/stop gates. No invented ROI. Just a way to find out whether AI-generated code in your repo is an asset or a liability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cheap-write, expensive-own problem
&lt;/h2&gt;

&lt;p&gt;Writing code is the cheapest part of software. AI made it nearly free. Reviewing, debugging, onboarding, and deleting code did not get cheaper. A 400-line generated diff costs the same review time, and every one of those lines becomes your debt forever.&lt;/p&gt;

&lt;p&gt;So "what does this cost per month?" is the wrong question. The right one is: "what does it cost to integrate and maintain this code for the next 90 days?"&lt;/p&gt;

&lt;p&gt;You need three things to answer it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A marker that identifies AI-authored commits&lt;/li&gt;
&lt;li&gt;A fixed budget so usage is an experiment, not a drift&lt;/li&gt;
&lt;li&gt;A churn ratio that turns maintenance pain into a number&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why a free tier is a measurement tool, not just a discount
&lt;/h2&gt;

&lt;p&gt;MonkeyCode is an open-source AI coding agent that gives teams free model access — 10 million tokens — and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;I'm not telling you to adopt it because it's free. I'm telling you to use its free tier the way you'd use any fixed-cost pilot environment: as a controlled budget. When the budget is visible, every generated line becomes a decision. Is this token spend worth the future maintenance? That's governance without a committee.&lt;/p&gt;

&lt;p&gt;The product is the vehicle. The experiment design is the actual tool. And the script below works no matter which agent you use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Mark your AI commits
&lt;/h2&gt;

&lt;p&gt;You can't measure what you can't identify. Make every AI-generated change carry a marker in the commit message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"[ai] add validation helper for webhook payloads"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one habit is the foundation of everything that follows. If your team already routes AI work through a specific bot identity, you can match on author instead. Pick one marker and make it a PR checklist item for two weeks until it sticks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Measure churn with this script
&lt;/h2&gt;

&lt;p&gt;Save this as &lt;code&gt;ai_churn.sh&lt;/code&gt; and run it from your repository root. It counts AI-added lines, then counts how many lines in those same files were changed by later commits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# ai_churn.sh — rough rework signal for AI-authored commits&lt;/span&gt;
&lt;span class="c"&gt;# Needs: git, awk. Set AI_MARKER to your team's convention.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;AI_MARKER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AI_MARKER&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;[ai]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;LOOKBACK_DAYS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOOKBACK_DAYS&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;90&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;FILTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;   &lt;span class="c"&gt;# optional path prefix, e.g. src/ or tests/&lt;/span&gt;

&lt;span class="nb"&gt;mapfile&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; commits &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;git log &lt;span class="nt"&gt;--since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOOKBACK_DAYS&lt;/span&gt;&lt;span class="s2"&gt; days ago"&lt;/span&gt; &lt;span class="nt"&gt;--grep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AI_MARKER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;%H&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;total_added&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;total_churn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="k"&gt;for &lt;/span&gt;c &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;commits&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git show &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format: | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'^$'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;added&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git show &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--numstat&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format: | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{a+=$1} END {print a+0}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;churn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

  &lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$files&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILTER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILTER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;continue
    &lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git log &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$c&lt;/span&gt;&lt;span class="s2"&gt;..HEAD"&lt;/span&gt; &lt;span class="nt"&gt;--numstat&lt;/span&gt; &lt;span class="nt"&gt;--pretty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;format: &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{a+=$1; d+=$2} END {print a+d+0}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;churn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;churn &lt;span class="o"&gt;+&lt;/span&gt; n&lt;span class="k"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;done

  &lt;/span&gt;&lt;span class="nv"&gt;total_added&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;total_added &lt;span class="o"&gt;+&lt;/span&gt; added&lt;span class="k"&gt;))&lt;/span&gt;
  &lt;span class="nv"&gt;total_churn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;total_churn &lt;span class="o"&gt;+&lt;/span&gt; churn&lt;span class="k"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"AI commits: &lt;/span&gt;&lt;span class="k"&gt;${#&lt;/span&gt;&lt;span class="nv"&gt;commits&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"AI lines added: &lt;/span&gt;&lt;span class="nv"&gt;$total_added&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Lines changed in those files after merge: &lt;/span&gt;&lt;span class="nv"&gt;$total_churn&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$total_added&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$total_churn&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$total_added&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'BEGIN {printf "Churn ratio: %.2f\n", a/b}'&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it per category instead of once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./ai_churn.sh src/
./ai_churn.sh tests/
./ai_churn.sh docs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The churn ratio is a rough signal, not a verdict. It ignores files that were renamed or deleted, and it counts all later modifications regardless of who made them. That's fine for a pilot. You want direction, not precision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Turn lines into a budget
&lt;/h2&gt;

&lt;p&gt;Now translate churn into something a finance person will believe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rework_hours = churn_lines / lines_touched_per_hour
rework_cost  = rework_hours × loaded_burden_rate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worked example, with your numbers replacing mine. Say the pilot produces 12,000 AI-added lines, and the churn ratio lands at 0.6. That's 7,200 lines touched again later. At a rough 150 lines per hour of touch-up work, that's about 48 developer-hours. At a $100/h loaded rate, &lt;strong&gt;$4,800 of rework&lt;/strong&gt; — before you count reviewing the original generations.&lt;/p&gt;

&lt;p&gt;Was the velocity worth $4,800? Maybe. Now it's a decision instead of a surprise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Apply gates per category
&lt;/h2&gt;

&lt;p&gt;The magic is that gates live at the category level, not the team level. A model can be excellent at generating test fixtures and terrible at auth middleware. You won't know which until you split the numbers.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Gate&lt;/th&gt;
&lt;th&gt;Churn ratio&lt;/th&gt;
&lt;th&gt;What you do&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;th&gt;Review date&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Expand&lt;/td&gt;
&lt;td&gt;&amp;lt; 0.30&lt;/td&gt;
&lt;td&gt;Allow broader use in that category; add more agent permissions&lt;/td&gt;
&lt;td&gt;Platform lead&lt;/td&gt;
&lt;td&gt;Day 90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restrict&lt;/td&gt;
&lt;td&gt;0.30 – 0.70&lt;/td&gt;
&lt;td&gt;One extra human reviewer; limit to non-critical paths&lt;/td&gt;
&lt;td&gt;Engineering manager&lt;/td&gt;
&lt;td&gt;Day 90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stop&lt;/td&gt;
&lt;td&gt;&amp;gt; 0.70&lt;/td&gt;
&lt;td&gt;Rewrite manually; keep the budget for one-off spikes&lt;/td&gt;
&lt;td&gt;Team lead&lt;/td&gt;
&lt;td&gt;Day 45 mid-check&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A scorecard like this is a conversation tool, not objective truth. The churn ratio can't tell you &lt;em&gt;why&lt;/em&gt; a file was touched again. Maybe the AI wrote bad code. Maybe requirements changed. Treat the gate as a forcing function for a human conversation, not as a robot judge.&lt;/p&gt;

&lt;p&gt;The important part is setting the thresholds before you see the data. Otherwise Day 90 becomes a debate about which number feels right.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the free server is for
&lt;/h2&gt;

&lt;p&gt;There's a second measurement hazard: environment drift. If the agent runs on a shared cloud seat for the first month and a local machine for the second, your churn numbers mix two different setups. MonkeyCode's free server option gives you a fixed place to run the pilot so the environment stays constant — useful for reproducible regeneration and for letting the review team poke at the same sandbox.&lt;/p&gt;

&lt;p&gt;Again, that's a convenience, not the point. You could run this whole experiment against any agent that lets you tag commits. The budget discipline and the churn gate do the heavy lifting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read this before you run it
&lt;/h2&gt;

&lt;p&gt;Who should &lt;strong&gt;not&lt;/strong&gt; use this workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Teams that won't tag AI commits.&lt;/strong&gt; Without the marker, the script measures nothing. Spend two weeks building the habit first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teams in the middle of a big refactor.&lt;/strong&gt; Churn will spike for reasons unrelated to AI quality, and the gates will yell false alarms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-developer repos.&lt;/strong&gt; A sample of five commits gives you a churn ratio with no statistical spine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One more honesty check: low churn doesn't prove the code is good. It might mean nobody dares to touch it. Pair this workflow with code review outcomes, not just git archaeology.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question that matters
&lt;/h2&gt;

&lt;p&gt;Teams are asking what happens to technical debt when AI makes code cheap. This is an answer you can run locally in an afternoon.&lt;/p&gt;

&lt;p&gt;What threshold would change your pilot decision — 0.4? 0.6? Set the number before you spend the tokens. Otherwise Day 90 is just another opinion meeting. If you want a budgeted sandbox to run this in, MonkeyCode's free tier is one way to get it. The script doesn't care which agent you use, and neither should your decision.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devtools</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>Free Models Drift. Lock Their Behavior With This 30-Line Gate.</title>
      <dc:creator>bestbee</dc:creator>
      <pubDate>Tue, 01 Sep 2026 12:38:44 +0000</pubDate>
      <link>https://dev.to/bestbee/free-models-drift-lock-their-behavior-with-this-30-line-gate-5a4n</link>
      <guid>https://dev.to/bestbee/free-models-drift-lock-their-behavior-with-this-30-line-gate-5a4n</guid>
      <description>&lt;p&gt;Last Tuesday, a review agent changed its voice. The endpoint, the prompt, and the model tier stayed the same, but a field that used to be a string became a nested object. The team noticed only because a smoke test broke; nobody had been watching the model itself. The incident started before the alert fired.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is a composite scenario, not a reported incident.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A free model in production is a moving target. Providers can retrain, swap, or throttle models without changing the API contract. This is the hidden dependency in many 2026 AI workflows: the interface stays put while the behavior underneath drifts.&lt;/p&gt;

&lt;p&gt;This article shows how to install a 30-line behavior lock that catches shape changes before they reach your product. The routine uses versioned prompts, a small eval set, and a reproducible gate script. It is deliberately vendor-neutral and aimed at teams standardizing on free model access.&lt;/p&gt;

&lt;p&gt;MonkeyCode is one of the tools making that decision low-friction because its free models and free server remove the usual sign-up cost for trying AI inside a dev workflow. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The script below does not depend on MonkeyCode; point it at any OpenAI-compatible chat completion endpoint you can reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Behavior Lock Beats Another Test Suite
&lt;/h2&gt;

&lt;p&gt;Unit tests check your code against a fixed expectation. An LLM endpoint checks nothing, because the model can change between two identical requests. A prompt file is not a contract; it is a hypothesis about how the current weights interpret a string.&lt;/p&gt;

&lt;p&gt;Free model tiers amplify this problem because the vendor rarely commits to output stability. The model you evaluate today may be a different model tomorrow. That is fine until your tooling expects &lt;code&gt;summary&lt;/code&gt; to be a string and the new model returns an object. A behavior lock verifies the contract on a schedule instead of assuming it. It cannot stop drift, but it makes drift visible in minutes rather than days.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Five Components of a Minimal Behavior Lock
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Versioned prompt files&lt;/strong&gt; in your repository, one JSONL file per prompt template generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An eval set&lt;/strong&gt; of 10 to 50 real inputs with a &lt;code&gt;required_fields&lt;/code&gt; map and expected types.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A gate script&lt;/strong&gt; that calls the endpoint, parses the response, and validates the schema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A pass-rate threshold&lt;/strong&gt; such as &lt;code&gt;MIN_PASS_RATE=0.95&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A human review drill&lt;/strong&gt; with an owner, a failure record, and a re-run date.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is not a benchmark. It is an alarm.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 30-Line Gate You Can Run This Week
&lt;/h2&gt;

&lt;p&gt;Save the file as &lt;code&gt;behavior_lock.py&lt;/code&gt;. It reads a JSONL file where each line contains an id, a prompt, and a map of required top-level fields and their expected JSON types.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_shape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;not-json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;required_fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;missing-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wrong-type-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wrong-type-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--endpoint&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--model&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--samples&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;choices&lt;/span&gt;&lt;span class="sh"&gt;'&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check_shape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;
    &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;passed&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pass_rate=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.3f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;passed=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;failed=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;failed_id=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; reason=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;minimum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;MIN_PASS_RATE&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.95&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;minimum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An example &lt;code&gt;eval_set.jsonl&lt;/code&gt; line looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"review-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Summarize this PR diff. Return valid JSON. Use a string field named summary and a string field named risk."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"required_fields"&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="nl"&gt;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"risk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&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;Run it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;AI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_key python behavior_lock.py &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--endpoint&lt;/span&gt; https://your-openai-compatible-endpoint/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model&lt;/span&gt; your-model &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--samples&lt;/span&gt; eval_set.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point the endpoint at the free server you are evaluating, and let the minimum pass rate default to &lt;code&gt;0.95&lt;/code&gt;. That threshold is a starting point, not a law. Teams handling regulated data often set it to &lt;code&gt;0.99&lt;/code&gt;, while exploratory work can tolerate &lt;code&gt;0.90&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the Signals, Not the Slogans
&lt;/h2&gt;

&lt;p&gt;The gate produces numbers, but the action depends on the pattern. Use this table as a review script when the gate fails.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Likely reading&lt;/th&gt;
&lt;th&gt;Gate action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pass rate drops below 0.95&lt;/td&gt;
&lt;td&gt;The model changed its output grammar&lt;/td&gt;
&lt;td&gt;Freeze the prompt, replay the baseline, and manually review 50 outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A required field disappears in more than 10% of samples&lt;/td&gt;
&lt;td&gt;Schema drift&lt;/td&gt;
&lt;td&gt;Add stricter format instructions and pin to a known model if you can&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pass rate recovers on a re-run&lt;/td&gt;
&lt;td&gt;Transient behavior, not a regression&lt;/td&gt;
&lt;td&gt;Log the event for trend review and keep the current model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p95 latency triples for the same workload&lt;/td&gt;
&lt;td&gt;Throttling or load on the free server&lt;/td&gt;
&lt;td&gt;Run a two-day A/B test against a paid endpoint before migrating&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provider release note appears in changelog&lt;/td&gt;
&lt;td&gt;Planned model replacement&lt;/td&gt;
&lt;td&gt;Re-run the gate before the next sprint and decide whether the new shape is acceptable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each row is a decision, not a report. The gate tells you that something moved; the table tells you whether to act on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations and Who Should Skip This Lock
&lt;/h2&gt;

&lt;p&gt;This gate only detects structural drift. It will not catch a model that becomes more verbose, less safe, or more creative. For prose-heavy outputs, you need an embedding fingerprint or a judge model instead of a field check. It also assumes your eval set is representative; if it contains only happy-path inputs, the pass rate will look better than reality.&lt;/p&gt;

&lt;p&gt;Teams doing one-off prototypes do not need this. If your prompt runs in a notebook and no product depends on it, a behavior lock is overhead. Small teams running fewer than a few thousand tokens per week can start with a manual spot-check instead. The gate earns its keep once the model output feeds an automated workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run It Before You Standardize
&lt;/h2&gt;

&lt;p&gt;Free models are an option, not a relationship. The real cost of adoption is the time needed to detect drift before it reaches users. The 30-line behavior lock makes that cost visible: it tells you when the model changes shape between two weeks of identical prompts.&lt;/p&gt;

&lt;p&gt;Start with ten samples, run the gate, record the pass rate, and re-run next week. You do not need a perfect benchmark; you need an alarm that works before the incident does.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Free AI Server or Self-Hosted Model? A 6-Variable Cost and Latency Decision Table</title>
      <dc:creator>bestbee</dc:creator>
      <pubDate>Mon, 31 Aug 2026 11:38:32 +0000</pubDate>
      <link>https://dev.to/bestbee/free-ai-server-or-self-hosted-model-a-6-variable-cost-and-latency-decision-table-22p2</link>
      <guid>https://dev.to/bestbee/free-ai-server-or-self-hosted-model-a-6-variable-cost-and-latency-decision-table-22p2</guid>
      <description>&lt;p&gt;Your PR queue just doubled overnight. AI agents merged code faster than any human reviewer can read it, and the retrospective is tomorrow.&lt;/p&gt;

&lt;p&gt;Everyone agrees on the fix: automate the first-pass review with an AI model. The fight breaks out over where that model runs. One teammate insists on a free hosted endpoint. Another wants a self-hosted 7B model on the spare GPU. Finance wants the paid API because “free is never free.”&lt;/p&gt;

&lt;p&gt;Let me get my bias out of the way: &lt;strong&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/strong&gt; MonkeyCode is an open-source project that offers free model access and a free server option for people who want to try AI-assisted workflows without a credit card. But that doesn't mean every team should use it. The next 15 minutes give you the decision table I use when choosing between a free hosted tier, self-hosting, and a paid API for a specific workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why “free” isn't the question
&lt;/h2&gt;

&lt;p&gt;Free is just one row in a spreadsheet. The actual question is: &lt;em&gt;which deployment option survives the constraints your team already has?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Latency, data residency, and ops burden always outrank sticker price. A free endpoint that returns in 25 seconds is useless if your CI pipeline times out at 10. A self-hosted GPU that nobody knows how to maintain is a worse liability than a $0.03 API call.&lt;/p&gt;

&lt;p&gt;So instead of arguing in meetings, score the options.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 6 variables that actually decide
&lt;/h2&gt;

&lt;p&gt;Define each variable before you look at any product. Use three levels: &lt;strong&gt;Low&lt;/strong&gt;, &lt;strong&gt;Medium&lt;/strong&gt;, &lt;strong&gt;High&lt;/strong&gt;. Or score 1–5 if you want numeric outputs.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Latency tolerance&lt;/strong&gt; — Can your workflow wait 5 seconds? 30 seconds? Does it need a hard SLO?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data sensitivity&lt;/strong&gt; — Do prompts contain customer PII, proprietary code, or regulated data? Can they leave your VPC?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load volatility&lt;/strong&gt; — Is traffic steady at 10 requests/minute, or does it spike to 1,000 after a release?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost ceiling&lt;/strong&gt; — What is the maximum monthly spend you can justify for this workload?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ops capacity&lt;/strong&gt; — Does anyone on the team own model infrastructure, or is that a full-time job they don't have?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control needs&lt;/strong&gt; — Do you need fine-tuned weights, custom sampling, or a specific model version pinned forever?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The decision table
&lt;/h2&gt;

&lt;p&gt;Fill this in with your actual numbers. Don't compare marketing pages — compare your own constraints.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Free hosted (e.g., MonkeyCode)&lt;/th&gt;
&lt;th&gt;Self-hosted open-weight&lt;/th&gt;
&lt;th&gt;Paid API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Medium; shared server, variable&lt;/td&gt;
&lt;td&gt;Low if GPU idle, high if contended&lt;/td&gt;
&lt;td&gt;Usually low, provider-dependent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data privacy&lt;/td&gt;
&lt;td&gt;Prompts go to an external server&lt;/td&gt;
&lt;td&gt;Data stays in your VPC&lt;/td&gt;
&lt;td&gt;Prompts go to an external provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load spikes&lt;/td&gt;
&lt;td&gt;Can burst, but fair-use limits apply&lt;/td&gt;
&lt;td&gt;Must provision capacity yourself&lt;/td&gt;
&lt;td&gt;Auto-scales, pay per token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Free up to offered limits&lt;/td&gt;
&lt;td&gt;Fixed hardware + electricity + maintenance&lt;/td&gt;
&lt;td&gt;Pay per token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;You&lt;/strong&gt; are the SRE&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control&lt;/td&gt;
&lt;td&gt;Limited to exposed features&lt;/td&gt;
&lt;td&gt;Full control of weights&lt;/td&gt;
&lt;td&gt;Limited to API surface&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table is a conversation tool, not objective truth. It exists to surface which variable is driving the debate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: a 50-person product team
&lt;/h2&gt;

&lt;p&gt;Say your team has these characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency tolerance&lt;/strong&gt;: Medium — a 20-second first-pass review is fine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data sensitivity&lt;/strong&gt;: Medium — no PII, but proprietary code snippets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load volatility&lt;/strong&gt;: High — 50 requests/day, then 500 right after a big merge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost ceiling&lt;/strong&gt;: $50/month.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ops capacity&lt;/strong&gt;: Low — nobody wants to babysit a GPU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control needs&lt;/strong&gt;: Low — no fine-tuning required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scores (1 = bad, 5 = good):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Free hosted&lt;/th&gt;
&lt;th&gt;Self-hosted&lt;/th&gt;
&lt;th&gt;Paid API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privacy&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load spikes&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;23&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;24&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Paid API barely wins. Free hosted is a breath away. If the cost ceiling were $200/month, paid would run away with it. If latency tolerance were “under 2 seconds,” free hosted would drop out of the race entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test it yourself: a 5-minute endpoint benchmark
&lt;/h2&gt;

&lt;p&gt;Don't trust my scores. Here's a reproducible script that measures latency and error rate against any OpenAI-compatible endpoint — a free server, your self-hosted model, or a paid provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# endpoint-bench.sh - measure latency and HTTP status for N requests&lt;/span&gt;
&lt;span class="c"&gt;# Usage: ENDPOINT=https://... API_KEY=... MODEL=my-model ./endpoint-bench.sh&lt;/span&gt;

&lt;span class="nv"&gt;ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ENDPOINT&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;https&lt;/span&gt;://your-endpoint.example/v1/chat/completions&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MODEL&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;PROMPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROMPT&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;Summarize&lt;/span&gt;&lt;span class="p"&gt; the key code review risks in this PR in 100 words&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;REQUESTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REQUESTS&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;10&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;MAX_TOKENS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MAX_TOKENS&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;100&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REQUESTS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s%N&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"/tmp/resp_&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;.json"&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;API_KEY&lt;/span&gt;:+-H&lt;span class="p"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;model&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$MODEL&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;messages&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:[{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;role&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$PROMPT&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}],&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;max_tokens&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$MAX_TOKENS&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ENDPOINT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s%N&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;elapsed_ms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;end &lt;span class="o"&gt;-&lt;/span&gt; start&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="m"&gt;1000000&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Request &lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;: HTTP &lt;/span&gt;&lt;span class="nv"&gt;$status&lt;/span&gt;&lt;span class="s2"&gt; in &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;elapsed_ms&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;ms"&lt;/span&gt;
  &lt;span class="nb"&gt;sleep &lt;/span&gt;0.2 &lt;span class="c"&gt;# gentle pacing to avoid self-inflicted rate limits&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it against each candidate endpoint, then feed the numbers back into the table. Watch for three patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tail latency&lt;/strong&gt;: median is nice, but the worst 10% of requests will break your pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP 429s&lt;/strong&gt;: free tiers and shared servers rate-limit. Count those.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts&lt;/strong&gt;: a 100-token response is not the same as a 2,000-token one. Benchmark your real prompt size.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When the answer flips
&lt;/h2&gt;

&lt;p&gt;Sensitivity analysis is where the table earns its keep. Change one variable at a time and see how many points move.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flip 1: Data sensitivity goes High.&lt;/strong&gt;&lt;br&gt;
If prompts contain regulated data, a free hosted endpoint becomes a hard no — regardless of latency or cost. Self-hosted or a provider with a signed VPC agreement is now mandatory. No score can override a compliance wall.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flip 2: Latency SLO goes to 2 seconds.&lt;/strong&gt;&lt;br&gt;
A shared free server probably can't hold a 99th percentile under 2s. You're down to a self-hosted GPU with idle capacity or a paid API with reserved concurrency. Budget math changes instantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flip 3: Load spikes 50x for one hour.&lt;/strong&gt;&lt;br&gt;
Cost ceilings with paid APIs can explode. Free servers may rate-limit or degrade. A self-hosted queue can gracefully shed load — if someone is on-call to watch it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should NOT use this approach
&lt;/h2&gt;

&lt;p&gt;This decision framework fails for three kinds of teams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Teams with zero tolerance for external data access.&lt;/strong&gt; No framework should talk you into sending customer data to a service you don't control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teams that need sub-second interleaving with local tools.&lt;/strong&gt; The network hop alone might kill you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teams that need custom fine-tuning.&lt;/strong&gt; A free hosted endpoint exposes what it exposes; you can't roll your own LoRA on someone else's fair-use quota.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those is you, stop reading and go talk to your security team. You already know your answer.&lt;/p&gt;

&lt;p&gt;The rest of us can run the script, fill in the table, and argue about the numbers instead of the vibes.&lt;/p&gt;

&lt;p&gt;When you're done, ask yourself this: &lt;em&gt;Which single variable — latency, privacy, cost, or ops — would have to change to flip your recommendation?&lt;/em&gt; That question, not the free tier, is what tells you where to run your AI workload.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>devtools</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Does Your AI-Generated Test Suite Actually Fail? Use Mutation Testing to Find Out</title>
      <dc:creator>bestbee</dc:creator>
      <pubDate>Sun, 30 Aug 2026 10:47:53 +0000</pubDate>
      <link>https://dev.to/bestbee/does-your-ai-generated-test-suite-actually-fail-use-mutation-testing-to-find-out-5djg</link>
      <guid>https://dev.to/bestbee/does-your-ai-generated-test-suite-actually-fail-use-mutation-testing-to-find-out-5djg</guid>
      <description>&lt;p&gt;The CI was green. Coverage was at 91%. The team felt great — until a production bug slipped through a function that supposedly had 100% line coverage. The tests were written by an AI assistant, and they were a lie: they asserted the output but never broke the implementation.&lt;/p&gt;

&lt;p&gt;That team is not an outlier. As coding agents generate more code, they also generate more tests, and those tests often share the same blind spots as the code. Coverage percentages measure how much code ran, not whether anyone checked that the code does the right thing. For a team evaluating a free model for test generation, coverage is the wrong success metric. Mutation score is the right one.&lt;/p&gt;

&lt;p&gt;This post shows how to run a mutation-testing gate on AI-generated unit tests, using a free model to generate the tests and a free server to execute the batch. You get a reproducible workflow, a cost estimate, and a decision table for when this approach makes sense.&lt;/p&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mutation testing in five minutes
&lt;/h2&gt;

&lt;p&gt;Mutation testing deliberately breaks your source code — changing &lt;code&gt;&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;lt;&lt;/code&gt;, removing a &lt;code&gt;return&lt;/code&gt;, swapping &lt;code&gt;+&lt;/code&gt; to &lt;code&gt;-&lt;/code&gt; — and then runs your test suite. Each broken version is a mutant. If the tests fail, the mutant is killed. If the tests still pass, the mutant survived, and you found a blind spot.&lt;/p&gt;

&lt;p&gt;The score is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mutation score = killed mutants / (total mutants - equivalent mutants)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A score above 80% means your tests actually catch behavior changes. Below 50% means you have a warm blanket of assertions that verify nothing.&lt;/p&gt;

&lt;p&gt;Popular tools: &lt;code&gt;mutmut&lt;/code&gt; for Python, &lt;code&gt;Stryker&lt;/code&gt; for JavaScript/TypeScript, &lt;code&gt;PIT&lt;/code&gt; for Java. All can run inside a CI job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why free models change the calculation
&lt;/h2&gt;

&lt;p&gt;Generating tests with an LLM is cheap in tokens but expensive in verification time. A decent unit test is 30–80 lines, roughly 400–1,000 tokens. Ten files of generated tests might burn 10–20K tokens per run. That fits comfortably inside a free model tier — MonkeyCode's free models cover this scale without a credit card, and the associated free server can run the mutation batch without touching your laptops or your paid CI minutes.&lt;/p&gt;

&lt;p&gt;Here is the workflow that turns a free model into a meaningful quality gate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate tests for a module with an LLM prompt that includes the function signature and a few examples.&lt;/li&gt;
&lt;li&gt;Run the generated tests against the current code. They should pass.&lt;/li&gt;
&lt;li&gt;Run the mutation tool. Compute the score.&lt;/li&gt;
&lt;li&gt;If the score is below threshold, expand the prompt with edge cases the mutants exposed.&lt;/li&gt;
&lt;li&gt;Re-run until the score crosses your bar.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is not a benchmark from a blog post. It is a gate you can run in your own repository with your own bugs.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reproducible Python example
&lt;/h2&gt;

&lt;p&gt;Assume you have a small module &lt;code&gt;pricing.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;discount must be between 0 and 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;discount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A naive AI-generated test might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_apply_discount&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;apply_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;apply_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That test executes both lines. Coverage is 100%. But raise a &lt;code&gt;ValueError&lt;/code&gt; or test a discount of exactly 0, and the test suite stays green because the assertions never exercise the guard clause.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;mutmut&lt;/code&gt; on it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;mutmut
mutmut run &lt;span class="nt"&gt;--paths-to-mutate&lt;/span&gt; pricing.py
mutmut results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see mutants like &lt;code&gt;discount &amp;lt; 0&lt;/code&gt; changed to &lt;code&gt;discount &amp;lt;= 0&lt;/code&gt;, and the tests will still pass. The mutation score will drop well below 60%, exposing the gap.&lt;/p&gt;

&lt;p&gt;Then you iterate the prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write unit tests for apply_discount. Include:
- a discount below 0 (expect ValueError)
- a discount above 1 (expect ValueError)
- boundary values 0 and 1
- a non-numeric input if type hints allow it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Regenerate, re-run &lt;code&gt;mutmut&lt;/code&gt;, and the score climbs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Budgeting tokens for this workflow
&lt;/h2&gt;

&lt;p&gt;Use this formula per module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tokens per iteration ≈ (base prompt 800) + (source lines × 15) + (generated test lines × 5)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a 200-line module, one iteration costs roughly 800 + 3000 + 800 = ~4.6K tokens. Three iterations per module, 8 modules: about 110K tokens. That is well inside the free tier of MonkeyCode's free models, and the free server handles the compute without a GPU on your side.&lt;/p&gt;

&lt;p&gt;A word of caution: the free server is best for bursty batch jobs, not for continuously hot endpoints. If you wire this into every commit, the queue may grow. Keep it on a nightly schedule or a manual trigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision table: should you adopt this gate?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Team situation&lt;/th&gt;
&lt;th&gt;Run mutation gate on AI tests?&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1–5 devs, early-stage project&lt;/td&gt;
&lt;td&gt;Yes, on the critical module only&lt;/td&gt;
&lt;td&gt;Cheap, catches the worst blind spots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20+ devs, monorepo, many AI-generated PRs&lt;/td&gt;
&lt;td&gt;Yes, as a required CI check&lt;/td&gt;
&lt;td&gt;Scales quality without human review time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests are purely integration/E2E&lt;/td&gt;
&lt;td&gt;No, mutation adds little&lt;/td&gt;
&lt;td&gt;Integration tests already exercise behavior end-to-end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security-sensitive code&lt;/td&gt;
&lt;td&gt;Yes, with a strict 90% threshold&lt;/td&gt;
&lt;td&gt;Mutation score is a proxy for adversarial thinking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No tests exist at all&lt;/td&gt;
&lt;td&gt;No, write a few baseline tests first&lt;/td&gt;
&lt;td&gt;Mutation needs a baseline to break&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The gate is a conversation tool, not a number to worship. It fails on untested legacy code, but that failure is information: you now know where your safety net is thin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations and who should not use this
&lt;/h2&gt;

&lt;p&gt;Free models have smaller context windows and may truncate long test files. Keep each module prompt under 2K tokens of source. The free server has rate limits, so a burst of 50 modules in one minute will likely fail; batch them in chunks of five.&lt;/p&gt;

&lt;p&gt;Teams with strict data-residency requirements should not send proprietary business logic to a hosted free model. For them, a self-hosted small model is the better route, even if the mutation gate still applies. And if your team has zero tolerance for false positives in test generation, a human-written suite with a high mutation score is still the gold standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it once before you judge the model
&lt;/h2&gt;

&lt;p&gt;Do not trust a vendor's benchmark for test generation. Clone a small service, ask the free model to write tests for one module, and run the mutation score yourself. That thirty-minute run tells you more than any leaderboard.&lt;/p&gt;

&lt;p&gt;MonkeyCode's free models and free server give you the resources to run that experiment today. The cost is zero; the evidence is yours.&lt;/p&gt;

&lt;p&gt;Set the gate on one module. Watch the score. Then decide whether the free model stays in your test pipeline.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Which Workloads Get the Free AI Server? A 4-Variable Fit Score With Worked Numbers</title>
      <dc:creator>bestbee</dc:creator>
      <pubDate>Sat, 29 Aug 2026 09:36:42 +0000</pubDate>
      <link>https://dev.to/bestbee/which-workloads-get-the-free-ai-server-a-4-variable-fit-score-with-worked-numbers-1hga</link>
      <guid>https://dev.to/bestbee/which-workloads-get-the-free-ai-server-a-4-variable-fit-score-with-worked-numbers-1hga</guid>
      <description>&lt;p&gt;Last week, a platform lead showed me a two-line brief: "We have free AI model access and a free server. Move the team onto it."&lt;/p&gt;

&lt;p&gt;By Wednesday, their customer-triage demo hit p95 latency of 14 seconds. The free server wasn't broken. The workload never belonged there — and nobody had asked the question that actually matters: which workloads earn the free lane, and which ones just burn it?&lt;/p&gt;

&lt;p&gt;Free access is not a product property. It's an allocation decision. Treat it like a shared lane: one bursty workload stalls every other passenger.&lt;/p&gt;

&lt;p&gt;The current DEV discussion around &lt;a href="https://dev.to/heinrichneb/ai-promoted-every-developer-to-reviewer-nobody-tested-the-reviewer-m4h"&gt;"AI promoted every developer to reviewer"&lt;/a&gt; points at the same root cause. Our workload mix shifted from generation to review — steady, async, summarization-shaped jobs. But most teams are still deciding what rides the free tier on vibes. Here's the tool I use to turn vibes into a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 4-Variable Fit Score
&lt;/h2&gt;

&lt;p&gt;Score every workload from 1 to 5 on four variables. Higher always means "fits a free hosted lane better."&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;5 =&lt;/th&gt;
&lt;th&gt;1 =&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Burstiness (B)&lt;/td&gt;
&lt;td&gt;Steady, predictable traffic&lt;/td&gt;
&lt;td&gt;Everything arrives in one 10-minute window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data sensitivity (D)&lt;/td&gt;
&lt;td&gt;Public or synthetic data&lt;/td&gt;
&lt;td&gt;Regulated PII or proprietary IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency tolerance (L)&lt;/td&gt;
&lt;td&gt;Async; minutes are fine&lt;/td&gt;
&lt;td&gt;Interactive; sub-second expected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost of failure (C)&lt;/td&gt;
&lt;td&gt;Worst case is a re-run&lt;/td&gt;
&lt;td&gt;Customer incident or missed compliance deadline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Weights are a conversation default, not objective truth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;total = 0.35 × B + 0.30 × D + 0.20 × L + 0.15 × C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burstiness carries the heaviest weight because a shared server's ceiling is set by its peak, not its average — one spike consumes the whole pool. (If you haven't measured your server's ceiling yet, do that first; this score assumes you know it.)&lt;/p&gt;

&lt;p&gt;One hard gate overrides every score: if D ≤ 2, the workload does not ride a free hosted lane. Full stop.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;Decision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;≥ 4.0, no hard gate&lt;/td&gt;
&lt;td&gt;Free lane candidate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.0 – 3.9&lt;/td&gt;
&lt;td&gt;Conditional: requires failover + an armed exit gate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt; 3.0&lt;/td&gt;
&lt;td&gt;Paid API or self-hosted, decided by unit economics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Score it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fit_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.15&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;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;xi&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;wi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xi&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;self-host or paid (hard gate: data boundary)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;free lane candidate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conditional: failover + exit gate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;paid API or self-host (unit economics)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;workloads&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;knowledge-base Q&amp;amp;A indexer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customer-ticket triage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;marketing draft generator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;workloads&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fit_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;knowledge-base Q&amp;amp;A indexer: 4.30 -&amp;gt; free lane candidate
customer-ticket triage: 1.70 -&amp;gt; self-host or paid (hard gate: data boundary)
marketing draft generator: 2.60 -&amp;gt; paid API or self-host (unit economics)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The row that surprises everyone
&lt;/h2&gt;

&lt;p&gt;The marketing generator scores lowest — and that's the row people argue with. Public data, latency-tolerant, worst case is a weak draft. Why 2.60?&lt;/p&gt;

&lt;p&gt;Burstiness. Everyone drafts campaigns on Tuesday morning. That spike lands in the same window as the KB indexer's daily wake-up and the support queue's peak. On a shared server, the worst-shaped workload sets the ceiling for everyone else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sensitivity: reshape the workload, not the server
&lt;/h2&gt;

&lt;p&gt;The score earns its keep when you move one variable.&lt;/p&gt;

&lt;p&gt;Queue drafts into batches of 20 and schedule them for 2 a.m.: burst score goes from 1 to 4.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;0.35×4 + 0.30×4 + 0.20×3 + 0.15×5 = 3.95&lt;/code&gt; → conditional.&lt;/p&gt;

&lt;p&gt;Move the internal review to noon instead of 9 a.m., and the failure cost is no longer customer-facing: &lt;code&gt;→ 4.30&lt;/code&gt; → free lane.&lt;/p&gt;

&lt;p&gt;Same server. Same model. Same workload purpose. Different shape. The score doesn't tell you which product is better; it tells you which workload is shaped to match the constraint. That's the difference between choosing a tool and designing for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tradeoff table you actually need
&lt;/h2&gt;

&lt;p&gt;The scorecard ranks fit; it doesn't price the alternatives.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Axis&lt;/th&gt;
&lt;th&gt;Free lane&lt;/th&gt;
&lt;th&gt;Paid API&lt;/th&gt;
&lt;th&gt;Self-hosted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to first value&lt;/td&gt;
&lt;td&gt;minutes&lt;/td&gt;
&lt;td&gt;minutes&lt;/td&gt;
&lt;td&gt;hours to days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data boundary&lt;/td&gt;
&lt;td&gt;hosted terms&lt;/td&gt;
&lt;td&gt;contract terms&lt;/td&gt;
&lt;td&gt;yours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency control&lt;/td&gt;
&lt;td&gt;shared, best effort&lt;/td&gt;
&lt;td&gt;SLA tiers&lt;/td&gt;
&lt;td&gt;full control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ops burden&lt;/td&gt;
&lt;td&gt;~zero&lt;/td&gt;
&lt;td&gt;low&lt;/td&gt;
&lt;td&gt;models, GPUs, monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exit cost&lt;/td&gt;
&lt;td&gt;low&lt;/td&gt;
&lt;td&gt;contract + migration&lt;/td&gt;
&lt;td&gt;sunk infra&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Vendor terms change — check current docs and price the exit, not just the entry. A blog post, including this one, is not a source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hard gates: owner, expiry, exit, archive
&lt;/h2&gt;

&lt;p&gt;A scorecard without governance is a slide deck.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Owner&lt;/strong&gt;: one engineer owns the scorecard and re-scores every workload every 30 days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry&lt;/strong&gt;: every score carries a date; none survives a quarter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exit&lt;/strong&gt;: a free-lane workload trips p95 above the agreed threshold twice in a week, or misses two deadlines in a month → demoted to paid or self-hosted. Use your own thresholds; the point is that they exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Archive&lt;/strong&gt;: when a workload leaves the free lane, write down why. That note becomes the first input of the next re-score.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where the free lane actually helps: the pilot
&lt;/h2&gt;

&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;

&lt;p&gt;MonkeyCode is an open-source project that ships with free model access and a free hosted server option. I'm not quoting quotas or benchmarks here — the numbers that matter are in the current docs, applied to your burst pattern, not mine.&lt;/p&gt;

&lt;p&gt;The structurally useful part is the shape: the free lane is a pilot lane. Run the scorecard until it says "free candidate", wire in one workload, and let the server confirm or refute the score over two weeks. If the only thing making free attractive is the quota, that's a coupon, not a fit.&lt;/p&gt;

&lt;p&gt;Two things I won't claim: that it fits every workload, or that free is permanent. If a workload carries regulated PII, no hosted option — free or paid — counts until you've reviewed the data terms. Hard gate beats high score, always.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who should not use this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No telemetry&lt;/strong&gt;: if you don't have burst or latency numbers, scoring just formalizes a guess.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prohibited hosting&lt;/strong&gt;: if regulations forbid hosted inference entirely, skip the score — self-host is the only lane.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Already-under-contract teams&lt;/strong&gt;: if the enterprise contract already zeroes out marginal token cost, the free lane's value collapses.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The reverse question
&lt;/h2&gt;

&lt;p&gt;Before you pick a lane, answer this: which single variable, if it moved by one point, would flip this workload into a different bucket?&lt;/p&gt;

&lt;p&gt;If you can't name one, the score is hiding a constraint you haven't measured — usually data sensitivity or a burst window.&lt;/p&gt;

&lt;p&gt;The server is not the decision. The workload shape is. Score the shape first, then let the infrastructure argue about latency.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>devex</category>
      <category>devops</category>
    </item>
    <item>
      <title>Your Free AI Server Has a Ceiling. Measure It in 30 Minutes Before the Team Does</title>
      <dc:creator>bestbee</dc:creator>
      <pubDate>Fri, 28 Aug 2026 03:11:52 +0000</pubDate>
      <link>https://dev.to/bestbee/your-free-ai-server-has-a-ceiling-measure-it-in-30-minutes-before-the-team-does-278i</link>
      <guid>https://dev.to/bestbee/your-free-ai-server-has-a-ceiling-measure-it-in-30-minutes-before-the-team-does-278i</guid>
      <description>&lt;p&gt;Tuesday, 10:47 AM. Fourteen developers open their IDE extensions at once, and the shared AI server starts returning timeouts. Nobody planned for the morning spike. The free tier was announced on Monday, the team adopted it by Tuesday, and the first capacity incident happened before lunch.&lt;/p&gt;

&lt;p&gt;This article is a 30-minute load-test workflow for teams that just received access to a free hosted AI server. The goal is not to benchmark model quality. The goal is to find the concurrency ceiling before your team does — the hard way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Free Server Is a Shared Resource Now
&lt;/h2&gt;

&lt;p&gt;MonkeyCode is an open-source AI coding project that offers free models and a free server. The offer is attractive for the same reason it is dangerous: it removes the two usual adoption barriers — API billing and self-hosting operations — and turns the server into a shared team resource overnight.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclosure: This article was prepared as part of MonkeyCode's product outreach.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A shared resource without a measured ceiling behaves like a shared database without connection pooling. It works in the demo, degrades under load, and fails at the worst possible moment: the morning standup, the release freeze, the day before the demo.&lt;/p&gt;

&lt;p&gt;The failure mode is not what most teams expect. It is not the token quota. It is latency collapse. Requests queue, timeouts cascade, and the IDE extension retries, which adds more load. The server does not die; it just becomes unusable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math: Little's Law for AI Requests
&lt;/h2&gt;

&lt;p&gt;Before writing any test code, define the model. Little's Law states that the average number of requests in a system equals the arrival rate multiplied by the average service time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;L = λ × W
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;L&lt;/code&gt; — average requests in the system (concurrency)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;λ&lt;/code&gt; — arrival rate, requests per second&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;W&lt;/code&gt; — average service time per request, in seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an AI server, &lt;code&gt;W&lt;/code&gt; is dominated by model inference time. A single code-generation request can take 10 to 40 seconds on a shared free server, depending on the model and the prompt length. That changes the math dramatically.&lt;/p&gt;

&lt;p&gt;Consider a team of 12 developers. Each developer sends one request every 3 minutes during active work. That is an arrival rate of &lt;code&gt;λ = 12 / 180 = 0.067&lt;/code&gt; requests per second. If the average request takes 25 seconds (&lt;code&gt;W = 25&lt;/code&gt;), Little's Law gives &lt;code&gt;L = 0.067 × 25 = 1.67&lt;/code&gt;. That looks fine.&lt;/p&gt;

&lt;p&gt;But the morning spike changes everything. After standup, all 12 developers send their first request within 30 seconds: &lt;code&gt;λ = 12 / 30 = 0.4&lt;/code&gt;, so &lt;code&gt;L = 0.4 × 25 = 10&lt;/code&gt;. Ten concurrent requests. If the free server's effective ceiling is below that, the queue grows without bound. This is why the token quota is the wrong thing to watch — the concurrency ceiling hits first.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 30-Minute Load Test
&lt;/h2&gt;

&lt;p&gt;The test plan has three phases: single-request baseline, ramp-up, and spike. The script below uses Python with &lt;code&gt;asyncio&lt;/code&gt; and &lt;code&gt;aiohttp&lt;/code&gt;. It sends requests to an OpenAI-compatible chat endpoint, which is the common interface for free hosted AI servers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;aiohttp&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;

&lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-free-server.example/v1/chat/completions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write a Python function that parses a CSV file and returns a list of dicts.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;REQUESTS_PER_WORKER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;probe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monotonic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_concurrency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;aiohttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ClientSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REQUESTS_PER_WORKER&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;tasks&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="nf"&gt;probe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;concurrency&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;results&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;run_concurrency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;concurrency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;statuses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;p50&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statistics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;median&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latencies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;p95&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latencies&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latencies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;concurrency=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;concurrency&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  p50=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p50&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;6.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p95=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p95&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;6.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s  failures=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;asyncio&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="nf"&gt;main&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 with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;aiohttp
python load_test.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output gives you the ceiling in one glance:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concurrency&lt;/th&gt;
&lt;th&gt;p50&lt;/th&gt;
&lt;th&gt;p95&lt;/th&gt;
&lt;th&gt;Failures&lt;/th&gt;
&lt;th&gt;Verdict&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;4.2s&lt;/td&gt;
&lt;td&gt;5.1s&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Baseline healthy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;6.8s&lt;/td&gt;
&lt;td&gt;9.4s&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Acceptable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;14.5s&lt;/td&gt;
&lt;td&gt;38.2s&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Degraded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;31.0s&lt;/td&gt;
&lt;td&gt;84.0s&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Ceiling exceeded&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ceiling is the concurrency level where p95 crosses 30 seconds or failures appear. That number is your team's budget. If the ceiling is 6, a team of 12 will exceed it every morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Capacity Decision Table
&lt;/h2&gt;

&lt;p&gt;Once the ceiling is measured, the decision becomes mechanical:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Team size&lt;/th&gt;
&lt;th&gt;Measured ceiling&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1–3 devs&lt;/td&gt;
&lt;td&gt;5+&lt;/td&gt;
&lt;td&gt;Free server is fine. Skip self-hosting.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4–8 devs&lt;/td&gt;
&lt;td&gt;5+&lt;/td&gt;
&lt;td&gt;Free server with a queue policy. Stagger start-of-day usage.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4–8 devs&lt;/td&gt;
&lt;td&gt;Below 5&lt;/td&gt;
&lt;td&gt;Add a local fallback model for routine completions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9+ devs&lt;/td&gt;
&lt;td&gt;Any&lt;/td&gt;
&lt;td&gt;Plan for self-hosting or a paid tier with a concurrency SLA.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table is a conversation tool, not objective truth. The real decision variable is the measured ceiling divided by your peak &lt;code&gt;L&lt;/code&gt; from Little's Law. If that ratio is below 1.5, you will feel the pain within a week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mitigations Before You Self-Host
&lt;/h2&gt;

&lt;p&gt;Self-hosting is not the first option. It is the last one, because it moves the operational burden back onto your team. Try these first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stagger the morning spike.&lt;/strong&gt; Ask developers to avoid sending their first request in the same minute. A 90-second window removes the worst of the burst.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce &lt;code&gt;W&lt;/code&gt;.&lt;/strong&gt; Shorter prompts and smaller &lt;code&gt;max_tokens&lt;/code&gt; values cut inference time. A request that takes 25 seconds at 512 tokens may take 12 seconds at 128 tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add a local fallback.&lt;/strong&gt; Route trivial completions to a local model and reserve the free server for complex tasks. This cuts arrival rate without changing team behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set a client-side timeout.&lt;/strong&gt; A 30-second timeout with a retry backoff prevents the retry cascade that turns a slow server into a dead one.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Who Should Skip This Entire Workflow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Teams with strict data-residency rules.&lt;/strong&gt; A free hosted server means prompts leave your network. No load test changes that. Skip the free tier entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single developers.&lt;/strong&gt; The ceiling is irrelevant when you are the only user. Just try the tool and watch your own latency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teams with production SLA requirements.&lt;/strong&gt; If the AI server is part of a customer-facing workflow, a free server without an SLA is a liability. Budget for a paid tier or self-hosted infrastructure from day one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Question I'd Ask You
&lt;/h2&gt;

&lt;p&gt;I do not care whether you like the free server. I care about one number: your measured ceiling, divided by your peak arrival rate times average service time.&lt;/p&gt;

&lt;p&gt;Run the 30-minute test, then compute &lt;code&gt;L&lt;/code&gt; for your team's worst hour. If the ratio is below 1.5, the free server will fail you at the worst moment — and the fix is a queue policy, a local fallback, or a budget line for self-hosting.&lt;/p&gt;

&lt;p&gt;That is the threshold that would reverse my recommendation. Measure it before the team does.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>performance</category>
      <category>testing</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
