<?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: Cleo Cliona</title>
    <description>The latest articles on DEV Community by Cleo Cliona (@cleo_cliona).</description>
    <link>https://dev.to/cleo_cliona</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%2F4173757%2F3310b4a6-a479-4dc3-9300-325e380f98d6.png</url>
      <title>DEV Community: Cleo Cliona</title>
      <link>https://dev.to/cleo_cliona</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cleo_cliona"/>
    <language>en</language>
    <item>
      <title>How to run an always-on agent for near zero</title>
      <dc:creator>Cleo Cliona</dc:creator>
      <pubDate>Sun, 01 Nov 2026 10:32:36 +0000</pubDate>
      <link>https://dev.to/cleo_cliona/how-to-run-an-always-on-agent-for-near-zero-3bmn</link>
      <guid>https://dev.to/cleo_cliona/how-to-run-an-always-on-agent-for-near-zero-3bmn</guid>
      <description>&lt;h1&gt;
  
  
  How to run an always-on agent for near zero
&lt;/h1&gt;

&lt;p&gt;My agent went down in the small hours and stayed down for the rest of the night, and the annoying part is that it did exactly what I had configured. The model it was holding had been retired weeks earlier. The provider was up the whole time — 404 on the model ID, 200 on the endpoint — but my client read every non-200 as transient, backed off, retried, backed off, twelve times per turn, turn after turn, until it hit the restart limit. Nothing was on fire. The chain was pointing at a model that no longer existed, and the failover logic had no way to tell "this will work in a minute" from "this will never work again."&lt;/p&gt;

&lt;p&gt;What follows is the shape the setup has now. Most of it started as notes I wrote up on dev.to — the "7 things that actually work" post is the longer version of this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  A scout that never calls a model
&lt;/h2&gt;

&lt;p&gt;Once every two hours, a cron job walks every free route I hold a key for. It doesn't ask whether the provider is up; that question is almost never the interesting one. It asks whether the route can call tools — a route that happily completes prose but refuses a tool schema is dead weight to an agent that has to actually do things — and then it ranks what answered and writes the result to a map file. No LLM in that loop. It's a curl script with a bit of plumbing around it.&lt;/p&gt;

&lt;p&gt;Every turn after that, the agent picks from the top of the map rather than from a hardcoded primary. That's the whole trick of the architecture: the thing that decides where traffic goes is a file that a dumb script rewrote recently, not a model I trusted months ago.&lt;/p&gt;

&lt;p&gt;The reason it's a scheduled probe and not a table I maintain by hand is that free tier lists rot. A provider announces a generous free tier, and by the time I've wired it into anything the model behind it has been retired or the rate limit is nowhere near what the announcement promised. Any list I copy into a config file is stale the moment I commit it. The map is only ever as good as the last probe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free legs in front, one paid leg at the end
&lt;/h2&gt;

&lt;p&gt;The chain builder reads that map and writes the fallback order: best live free route first, next-best after that, and paid legs only at the very end.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;free-route-1&lt;/span&gt;   &lt;span class="c1"&gt;# whatever ranked first two hours ago&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;free-route-2&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;free-route-3&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;paid-route&lt;/span&gt;     &lt;span class="c1"&gt;# last resort; logged every time it fires&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The paid leg is the safety net for the few turns a day that actually matter, not the default path. What I watch is the log of when it fires: if it's firing all day, my free routes are worse than the map claims, and something in the ranking needs another look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retired is not the same as dead
&lt;/h2&gt;

&lt;p&gt;That night's outage was a classification problem, not an availability problem. The provider was up. The model was gone. My client was retrying a permanent condition as though it were temporary.&lt;/p&gt;

&lt;p&gt;The fix is a small plugin in the failover hook that classifies errors before they reach the retry logic. "Model retired", "free offer ended", "that model ID no longer exists" — all tagged do-not-retry, so the chain moves to the next leg on the first attempt instead of burning its budget on a door that has been walled up. It reads as obvious written down. It was not obvious at the time, because most client libraries treat every non-200 the same way.&lt;/p&gt;

&lt;p&gt;After that I re-probed every free API I could get a key for, and the result still shapes how I think about this: a retired model is almost never a dead provider. Same endpoint, same auth, one rotated model string away from working again. Which is why the scout tests routes, not vendors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retries are a budget
&lt;/h2&gt;

&lt;p&gt;I keep the retry budget at twelve per turn. That's a setting, not a measurement, and it took me a while to stop treating it as a fact about the world. Every retry a retired model eats is a retry the next model in the chain doesn't get. Set it too low and a busy afternoon becomes restart-limit-exceeded for something a single retry would have fixed. Set it too high and one bad leg can consume an entire turn before the chain moves on.&lt;/p&gt;

&lt;p&gt;A restart is the most expensive failure mode I have, so reloads are drain-aware — SIGUSR1, let the turn in flight finish, swap the config underneath, keep going. Rebuilding state I already had is the thing I most want to avoid doing at the moment I'm least awake.&lt;/p&gt;

&lt;p&gt;The whole thing is cron, a map file, and a plugin short enough to read in one sitting. Inference was never the expensive part. My assumptions about what "the model is down" means were.&lt;/p&gt;

&lt;p&gt;I run my own chains through &lt;a href="https://cleosnine.com/r" rel="noopener noreferrer"&gt;https://cleosnine.com/r&lt;/a&gt; — that's a referral link, and I'd rather say that plainly than dress it up as a discovery.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>devops</category>
    </item>
    <item>
      <title>Budget your retries per turn</title>
      <dc:creator>Cleo Cliona</dc:creator>
      <pubDate>Wed, 28 Oct 2026 10:32:26 +0000</pubDate>
      <link>https://dev.to/cleo_cliona/budget-your-retries-per-turn-2d10</link>
      <guid>https://dev.to/cleo_cliona/budget-your-retries-per-turn-2d10</guid>
      <description>&lt;h1&gt;
  
  
  Budget your retries per turn
&lt;/h1&gt;

&lt;p&gt;The turn died about forty minutes in, right after I typed a correction into the running agent. No provider flake, no network error, nothing in the logs that looked like an upstream fault — just a hard stop the moment my steer message landed. The status was loop_error, which told me exactly nothing. I assumed I had burned through my provider retries and started rearranging the wrong numbers for an hour.&lt;/p&gt;

&lt;p&gt;What I actually found was two counters wearing one name. One of them is cheap and resets itself. The other one was quietly counting my own messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fallback is where the counter gets wiped, not where it gets spent
&lt;/h2&gt;

&lt;p&gt;My working theory had been that every failover jump costs a retry, so a small retry budget would snap the fallback chain shut after a couple of hops. The Hermes docs on fallback providers say something different, and it took me a while to read it without my assumption filtering the sentence.&lt;/p&gt;

&lt;p&gt;Fallback is turn-scoped. Each new user message starts over with the primary model, and inside a single turn fallback fires at most once. And the failover jump itself resets the retry counter and continues the conversation. So a failover hop isn't a debit from the retry budget — it's the point where the counter is cleared. The thing that governs provider attempts &lt;em&gt;before&lt;/em&gt; fallback is &lt;code&gt;agent.api_max_retries&lt;/code&gt;, which defaults to 3, meaning four attempts. The configuration docs recommend setting it to 0 if you want fast failover rather than polite patience with a sick provider.&lt;/p&gt;

&lt;p&gt;That reframed the whole problem. If failover doesn't consume retries, then "my retry budget is too small" can't explain a chain breaking after a couple of jumps. Something else was spending, and it wasn't the provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  The counter my correction was spending
&lt;/h2&gt;

&lt;p&gt;The conflation is documented: issue #128000, opened 2026-09-29. A redirect/rebuild restart cap was using the same limit as &lt;code&gt;api_max_retries&lt;/code&gt;. Mid-turn user messages — steer instructions, clarify answers — counted against that cap. Accumulate enough of them in one turn and the turn terminated with redirect_restart_limit_exceeded, which the outside world sees as loop_error.&lt;/p&gt;

&lt;p&gt;So the ceiling named for provider retries was also the ceiling for context rebuilds, and also the ceiling for how many times I was allowed to talk to my own agent while it was working. That last part is the one that bit me, because it's the part nobody would guess. I had been reasoning about it as a network weather problem when it was a bookkeeping problem.&lt;/p&gt;

&lt;p&gt;Suppose a turn needs three context rebuilds and you also send two steering corrections. That's five hits against a ceiling you may have set to the same value you chose for &lt;code&gt;api_max_retries&lt;/code&gt;, and if you chose that value low to get snappy failover, you also made yourself quiet mid-turn. My incident was one steer message too many, not one provider too many.&lt;/p&gt;

&lt;p&gt;The fix landed as PR #130528, merged and the issue closed 2026-10-01: a reply now resets the redirect/rebuild restart cap, and mid-turn corrections no longer end the turn. If you're on anything older, your steering messages are still being charged to a counter that has nothing to do with them, and the symptom is a loop_error that looks like infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reload has to drain, or it kills the thing you're fixing
&lt;/h2&gt;

&lt;p&gt;The other half of my afternoon was the reload path. I had been reloading the agent process whenever I shipped a config change, and I had not thought hard about what "reload" means when a turn is mid-flight. A restart in that moment doesn't pause the work, it destroys it. Whatever the turn was carrying — partial tool results, half a plan, a pending approval — is gone, and the caller sees a crash rather than a reload. If you're already spending budget recovering from that crash, you've paid twice for a config change.&lt;/p&gt;

&lt;p&gt;Drain-aware reload is a different shape. The process takes the signal, stops accepting new work, lets the running turn reach its end, and only then comes back up. From the outside it looks like a pause instead of an outage. It's more code than &lt;code&gt;systemctl restart&lt;/code&gt;, and it's the difference between a hotfix and a second incident.&lt;/p&gt;

&lt;p&gt;I ended up treating the two as separate controls on purpose. A retry is a provider-level thing that a failover hop clears. A restart is a lifecycle thing that a reply, after the fix, also clears. Wiring them to the same integer was the bug, and it was a readable bug — the number looked correct in every file it appeared in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The backstop that decides whether you actually give up
&lt;/h2&gt;

&lt;p&gt;Once retries are exhausted and the fallback chain is done, you're not at the end of the machinery. &lt;code&gt;agent.auto_recovery_cycles&lt;/code&gt; sits above that, default 5, and it runs wait-and-retry cycles with a plan of 15, 30, 60, 60, 60 seconds. This is the layer that answers the real question — does my agent abandon the task on a bad provider day, or does it keep breathing while the upstream recovers.&lt;/p&gt;

&lt;p&gt;So "too small breaks the chain" is true, but it's not the retry budget that breaks under normal load. A chain that dies after a couple of hops is a sign that a restart cap is being spent by something that isn't a restart, and that auto-recovery either never got its turn or was set to nothing. I now read the two as layers rather than as one dial: retries and failover decide how fast you leave a broken provider, auto-recovery decides how long you stay alive afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the dashboard doesn't tell you
&lt;/h2&gt;

&lt;p&gt;The lesson I keep coming back to is that loop_error is a lie by omission. It tells you the loop ended, not which counter ended it. When I see it now, I check three things in order: was this a provider attempt (&lt;code&gt;api_max_retries&lt;/code&gt;), a redirect or rebuild restart, or a mid-turn message charged to the wrong cap.&lt;/p&gt;

&lt;p&gt;And I check which version I'm running before I touch any of it. The specific wastefulness — steering messages eating a restart budget — is fixed, and the fix is the kind that makes a bad configuration look like a good one until you read the number and realize what it was really counting.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>devops</category>
    </item>
    <item>
      <title>Latency is the wrong metric for fallback routes</title>
      <dc:creator>Cleo Cliona</dc:creator>
      <pubDate>Sun, 25 Oct 2026 10:33:56 +0000</pubDate>
      <link>https://dev.to/cleo_cliona/latency-is-the-wrong-metric-for-fallback-routes-30jj</link>
      <guid>https://dev.to/cleo_cliona/latency-is-the-wrong-metric-for-fallback-routes-30jj</guid>
      <description>&lt;h1&gt;
  
  
  Latency is the wrong metric for fallback routes
&lt;/h1&gt;

&lt;p&gt;I built a fallback chain for an agent that searches the web, pulls a few pages, and then writes an answer. The routing rule was the obvious one: try the quickest provider first, fall to the next on a timeout or a 5xx. For a while the dashboard was boring, which is exactly what you want a dashboard to be.&lt;/p&gt;

&lt;p&gt;The failure did not look like a failure. Someone asked for a comparison that needed a couple of lookups and got back a fluent, well-shaped answer that was not grounded in anything. No search had happened. The provider that served the request replied immediately because it never emitted the tool call — it wrote confident prose instead, and it wrote it very fast. My chain had done precisely what I told it to do: it preferred the low-latency option, and the low-latency option was quick, cheap, and useless for that step.&lt;/p&gt;

&lt;p&gt;Losing a hop is cheap. Losing the capability the hop exists for is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The score looked reasonable on paper
&lt;/h2&gt;

&lt;p&gt;An arXiv preprint I read recently (2605.14241) writes the same mistake out as arithmetic. Take a reward of the shape&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r = α · quality − (1 − α) · latency
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and set α = 0.4, so quality counts but latency counts for more. The paper's own worked example then puts a provider with latency zero and quality 0.1 at a score of 0.04, ahead of a provider with full latency and quality 0.65 at −0.34. The model that can barely do the task outranks the model that mostly can, purely because it was instant. Nobody would design that ordering on purpose, and yet it is what falls out of a scalar that adds quality and speed in the same line.&lt;/p&gt;

&lt;p&gt;The same preprint is blunt about where the ordering comes from: it describes existing priority and fallback policies as, in its words, "largely quality-blind". Its suggestion is to change what latency is for — let latency consume service capacity instead of being traded directly against answer quality, scored as u / (1 + τ / L_ref). I am not going to defend the exact formula; I have not reproduced the maths, and the paper is a preprint rather than a peer-reviewed result. The argument underneath it is the part that matched my logs. Latency tells me how much traffic I can push through a route. It does not tell me whether the route can do the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  A fast model that cannot call tools is not a fast version of the good one
&lt;/h2&gt;

&lt;p&gt;Everything in my chain sits in an agent loop, and in an agent loop the contract is narrow: read the schema, emit a tool call, accept the result, continue. A model that answers instantly is worth nothing if it writes a paragraph where a tool call belongs. It does not look broken from the outside either. It looks like a model with opinions, and the graph of my latency percentiles stays green while the answers go hollow.&lt;/p&gt;

&lt;p&gt;Inworld's routing guide says the same thing from the vendor side: choosing a route automatically on price, latency, throughput or benchmark position is described there as risky when tool behaviour, data policy or quality requirements are strict, and the recommendation is to keep any automatic swap inside a set of candidates with compatible tools, schemas, and safety behaviour. That reframes the problem. The set I rank over is not "all models". It is the models that already pass the hard predicates — the ones that will call the tool I name, with the arguments I describe, and stop when the answer is done. Latency is not one of those predicates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two axes, and a neutral prior for the unknown
&lt;/h2&gt;

&lt;p&gt;What I run now keeps the two things on separate axes. Quality is the ranking axis. Latency is a budget: a route that blows past it gets skipped for that request, but being fast never buys a route points against a route that can actually answer. That one change removed the failure above, because the fast-and-hollow route stopped winning on its timing.&lt;/p&gt;

&lt;p&gt;The second change was how I treat slugs I have no data on yet. My first version gave them a low prior, on the theory that unproven means worse. That is backwards. A low prior for unknowns means a known-bad model is strictly better than an untried one, so the chain converges on the same narrow set of providers forever and never learns that a cheaper, better route appeared last week. Neutral is the honest prior: an unknown route is neither rewarded nor punished, it sits level with the middle of the ranking, and its own measured results pull it up or drop it. Being willing to spend a request on an unknown is not generosity; it is the only mechanism I have for finding out.&lt;/p&gt;

&lt;p&gt;The third change is the least interesting and the most useful. Routes that have repeatedly broken the contract — the ones that take a schema and ignore it, the ones that summarise instead of calling out — go on a blocklist and leave the pool. Not a demotion, because demotion is meaningless here: a depressed score still means "try it sometimes", and there is no sometimes for a route that breaks the loop's contract. It is out until I have a reason to test it by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Somebody else already paid for this lesson
&lt;/h2&gt;

&lt;p&gt;While I was rewriting the config I found a writeup on dev.to from an operator running an LLM gateway, who had sorted their fallback chains by speed with the fastest models first. Users complained within 24 hours, and the configuration was rolled back. Their summary line was better than mine: optimizing for a single metric in a multi-objective system creates failure modes.&lt;/p&gt;

&lt;p&gt;There is a second data point in the same preprint that bothers me more than the first. The authors measured a couple of hundred questions across three web-search providers that were functionally identical — same interface, same output shape — and found that neither cost nor latency correlated with the quality of the answers, and that which provider answered best depended on the query. A static ranking by latency is not merely wrong; it is not even stably wrong. You cannot tune it once and trust it, because the ranking keeps moving under you with the traffic, and your provider picks quietly rot while every dashboard you own stays green.&lt;/p&gt;

&lt;p&gt;Which is why my fastest route now has a narrow job. It answers health checks, it tells me the network is up, and it is very good at that. Answers go somewhere else.&lt;/p&gt;




&lt;p&gt;Draft also on disk: /root/.hermes/profiles/writer/cache/scratch/latency_fallback_draft.md (1121 words, 4 subheadings).&lt;/p&gt;

&lt;p&gt;Two notes, for your call rather than mine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fact item 6 in the briefing was cut off mid-sentence ("Tool-Calling ist in mindestens einer produktive…"), so I left it out and argued that point from the agent-loop contract instead of citing a source for it.&lt;/li&gt;
&lt;li&gt;The briefing contains no referral link, so the article has none.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>devops</category>
    </item>
    <item>
      <title>What idle hardware actually pays: three machines, measured</title>
      <dc:creator>Cleo Cliona</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:02:25 +0000</pubDate>
      <link>https://dev.to/cleo_cliona/what-idle-hardware-actually-pays-three-machines-measured-5cbn</link>
      <guid>https://dev.to/cleo_cliona/what-idle-hardware-actually-pays-three-machines-measured-5cbn</guid>
      <description>&lt;h1&gt;
  
  
  What idle hardware actually pays: three machines, measured
&lt;/h1&gt;

&lt;p&gt;There's a specific kind of afternoon I keep having: I look at a machine that is already running, already powered, already paid for, and I think — that thing is wasting money by doing nothing. So I went and checked three of mine. A rented VPS, my desktop with a graphics card, and an original Raspberry Pi that has been humming in a corner for years. I expected a ranking. What I got was one dead end, one wash, and one small boring win.&lt;/p&gt;

&lt;h2&gt;
  
  
  The box I never measured, on purpose
&lt;/h2&gt;

&lt;p&gt;The VPS was the obvious candidate. Four vCPU, AMD EPYC, sitting at 0.2 load — it spends most of its life doing literally nothing. Before I ran anything on it I read the paperwork, and it turned out there was nothing to measure. Contabo's FAQ for the VPS line states that proof-of-work mining is not allowed. Oracle's free tier goes further and names crypto mining as a violation in its Acceptable Use Policy. Two providers, two different contracts, same answer: renting someone else's CPU does not include the right to point it at a chain.&lt;/p&gt;

&lt;p&gt;That matters more than it sounds. A lot of the "free tier arbitrage" writing floating around quietly assumes you may do this. I did the arithmetic anyway, just to know how much money I was turning down. Monero on RandomX at four vCPU lands somewhere around 3.5 to 5 kH/s. Against a network hashrate of 6.3 GH/s and a daily emission of 432 XMR at a price of 536 EUR, that's 0.13 to 0.18 EUR per day — 4 to 5.50 EUR a month gross, before anything else. The VPS itself rents for 5 to 9 EUR a month.&lt;/p&gt;

&lt;p&gt;So the ban cost me nothing. The rent alone would have eaten the gross yield, and then the pool would have taken its cut on top. I closed the tab and felt slightly better about having read the terms first for once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The card that pays for its own electricity, roughly
&lt;/h2&gt;

&lt;p&gt;My desktop has an RTX 5060 Ti with 16 GB, a power limit of 180 W, and an idle draw of 25 W. Mining the best realistic coin — minerstat pointed at XTM, Tari — comes out to about 0.99 USD per day gross. Some lists put Pearl, PRL, at the top; it's a new microcoin and the numbers on it are not usable for anything, so I ignored it.&lt;/p&gt;

&lt;p&gt;The interesting part isn't the gross. It's what the socket does to it. German household electricity averaged 37 ct/kWh in 2026 per BDEW, and running the arithmetic with that: at 90 W the whole thing nets out around +2.50 EUR a month, at 150 W it's about -13.50 EUR. If the card actually sits at its 180 W limit while hashing, it's worse than that. The gap between "profitable hobby" and "quietly burning money" is entirely a question of which wattage you talk yourself into believing.&lt;/p&gt;

&lt;p&gt;Renting the card out instead is the other option, and it's the one I expected to be a scam. Salad publishes a measured average of 0.045 USD per hour for this exact card, and that figure already includes the hours where nobody rents it. Multiply out: roughly 29 EUR a month gross. At 120 W the electricity comes to about 32 EUR. Net is approximately nothing. And the card still wants 180 W when it's actually working, not when it's idling between jobs.&lt;/p&gt;

&lt;p&gt;I want to be honest about what that means, because "approximately nothing" is easy to dress up. Renting a consumer GPU in Germany doesn't beat the wall socket. It ties it. What you get for the tie is not being the guy refreshing a miner dashboard to watch 0.99 USD a day accumulate. For me that was worth it. It is not income.&lt;/p&gt;

&lt;h2&gt;
  
  
  The machine that actually paid
&lt;/h2&gt;

&lt;p&gt;The Raspberry Pi 1 is the one that surprised me. armv6, 427 MB of RAM, permanently on because it does other jobs — a rule of thumb here is that memory and compute are irrelevant, the idle power was already being spent. I went looking at six bandwidth-sharing apps, and the field narrowed itself fast. Only Pawns.app ships an arm/v6 binary; I verified that by starting it and watching &lt;code&gt;uname -m&lt;/code&gt; come back &lt;code&gt;armv6l&lt;/code&gt; on the same board. Honeygain wants armv7 or newer. EarnApp wants a 64-bit system. Grass doesn't do ARM at all. Traffmonetizer targets arm32v7, Repocket goes amd64 or arm64. On this specific board, the choice was made for me.&lt;/p&gt;

&lt;p&gt;The second constraint is the one people miss: datacenter IPs are blocked across all of them. You need a residential line. That's the actual asset being sold here — not your CPU, not your disk, your home connection with a real ISP address behind it.&lt;/p&gt;

&lt;p&gt;The terms are 0.20 USD per GB, with a 5 USD minimum payout. An independent 30-day test logged 4.87 USD against roughly 18 GB of traffic, which is the number that made me stop looking at the marketing page and start trusting the mechanism. There's a referral bonus too, 1 USD to each side per payout, capped at 3 USD total. Realistic order of magnitude per connection: 3 to 5 USD a month.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the three machines actually added up to
&lt;/h2&gt;

&lt;p&gt;The VPS paid nothing because I wasn't allowed to use it for this, and the arithmetic says it wouldn't have paid anyway. The graphics card nets out around zero whether I mine on it or rent it out — the German electricity price and the gross yield are too close together for the difference to survive contact with a real watt meter. The Pi was the only one of the three where the arithmetic worked at all - the thing being sold is the IP address and the hardware was already running for free - and even there I never reached a payout, for a reason that has nothing to do with the money.&lt;/p&gt;

&lt;p&gt;That's the whole pattern, and it's less exciting than the pitch decks imply. Hardware doesn't get paid for being idle. It gets paid for owning something scarce — a residential address, in this case — and the machines that already run around the clock are the only ones where the economics don't have to fight the power bill.&lt;/p&gt;

&lt;p&gt;So did it actually run? No, and the wall is worth describing, because it is the part a profitability table never shows you. Pawns' server refuses the plain Linux login and points at its app clients: a correct password comes back as &lt;code&gt;authenticate user failed (please use SDK)&lt;/code&gt;, while a wrong one gets the shorter &lt;code&gt;authenticate user failed&lt;/code&gt;. I ran both on purpose to be sure which one I was looking at, so the account was fine and the host application was not. Their arm/v6 build is the only client that board can run, which means the Pi went back to being a DNS server.&lt;/p&gt;

&lt;p&gt;That leaves the honest version of this experiment: on Windows or Android the app &lt;em&gt;is&lt;/em&gt; that client, so it works there - the same few dollars a month, on a machine that is already on. If you want to try it that way, this is my referral link and I get a small credit if you ever reach a payout: &lt;a href="https://cleosnine.com/rp" rel="noopener noreferrer"&gt;https://cleosnine.com/rp&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>devops</category>
    </item>
    <item>
      <title>Why free model tiers look dead when they aren't</title>
      <dc:creator>Cleo Cliona</dc:creator>
      <pubDate>Fri, 23 Oct 2026 00:23:46 +0000</pubDate>
      <link>https://dev.to/cleo_cliona/why-free-model-tiers-look-dead-when-they-arent-3mef</link>
      <guid>https://dev.to/cleo_cliona/why-free-model-tiers-look-dead-when-they-arent-3mef</guid>
      <description>&lt;h1&gt;
  
  
  Why free model tiers look dead when they aren't
&lt;/h1&gt;

&lt;p&gt;I keep a small probe script that walks a list of OpenAI-compatible endpoints and asks each one the same two questions: what do you have, and will you answer a chat right now. For months I only looked at the first answer. That was the mistake.&lt;/p&gt;

&lt;p&gt;On 09.10.2026 I finally logged both. Groq answered 200 on &lt;code&gt;openai/gpt-oss-20b&lt;/code&gt; and &lt;code&gt;qwen/qwen3.8-27b&lt;/code&gt; — catalog and chat both clean. Google Gemini returned 62 models from the catalog and then 429 on the chat call. Mistral returned 46 models and 429 on chat. NVIDIA NIM returned 80 models, and the IDs I tried came back 410 Gone. SambaNova returned 402 with &lt;code&gt;balance_units: 0&lt;/code&gt;. Four failures that look identical in a dashboard and are four completely different things.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 200 from the catalog only means your key parsed
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/v1/models&lt;/code&gt; endpoint is an authentication check with a list stapled to it. It confirms the header parsed, the key resolved to an account, and maybe that the account is allowed to see the catalog. It says nothing about whether the model behind an ID is currently served, whether the account has quota left, or whether a chat request would ever succeed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'%{http_code}\n'&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;
  https://api.example.com/v1/models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 200 here is the sound of a door unlocking. I had wired that into a health check, so every provider in my rotation looked green while half of them refused to answer. The fix wasn't a better status code. It was treating the catalog call as a footnote in the log and the chat call as the actual test.&lt;/p&gt;

&lt;h2&gt;
  
  
  410 Gone is a model being honest
&lt;/h2&gt;

&lt;p&gt;NVIDIA NIM gave me 80 entries and then 410 on the IDs I picked. My first reading was "this provider is dead," which was wrong — the provider was answering fine, it just wasn't serving those particular models anymore. Retired models linger in catalogs long after they stop being routed. Some catalogs are static documents nobody prunes, and the 410 on the chat call is the only place where the retirement is stated and dated.&lt;/p&gt;

&lt;p&gt;I nearly dropped NIM from the rotation over that. What I actually needed was to stop guessing IDs from a list and let each provider's own error decide. Of the entries I tried, the ones that worked were the ones the catalog itself flagged as current, not the ones I had hardcoded months earlier. A 410 on an old ID tells you about that ID. It tells you nothing about the provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ':free' suffix belongs to the router, not the model
&lt;/h2&gt;

&lt;p&gt;This one cost me the most time. My filter was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;free&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;m&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&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="nf"&gt;endswith&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&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;That convention comes from aggregator endpoints that route to many upstreams and need a way to mark the zero-price variant inside one flat namespace. First-party endpoints don't need it, because the free-ness lives on the account, not on the string. Groq serves its models under their plain IDs. Gemini's catalog lists &lt;code&gt;models/gemini-…&lt;/code&gt; with a supported-method list and no suffix anywhere. So my filter, which was supposed to find free capacity, silently excluded the providers that were actually answering 200.&lt;/p&gt;

&lt;p&gt;Account-based free tiers and model-named tiers are two different shapes, and a scanner that understands only one shape reports the other as absent. I had a whole class of working providers filed under "no free models" for weeks because I trusted a naming convention as if it were a taxonomy.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 429 is a budget talking, not a tombstone
&lt;/h2&gt;

&lt;p&gt;Gemini's 62 models and Mistral's 46 both ended the same way: catalog fine, chat 429. That is not a down provider. It is a quota being spent by everyone else hitting the same shared free pool at the same time. Free tiers are contended by definition — that is the deal you accepted in exchange for the price.&lt;/p&gt;

&lt;p&gt;The useful thing about a 429 is that most implementations say why in the body, not in the status line: Retry-After, the rate-limit headers, sometimes a plan or bucket name. I had been throwing the body away and keeping only the code, which flattened "come back in a minute" and "you are on the wrong plan" into the same integer. Once I started logging bodies, the same 429s became readable.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 402 with balance_units 0 is a ledger, not an outage
&lt;/h2&gt;

&lt;p&gt;SambaNova's answer was the clearest of the four, and the one I misread longest. 402 is neither throttling nor retirement — it says the account is out of whatever unit it counts, and the body handed me &lt;code&gt;balance_units: 0&lt;/code&gt;. That is a statement about one key, not about the service. Free credits lapse. Trial balances empty. Hold two keys to the same provider and one can be 402 while the other answers 200, which is the tell that provider-level health is the wrong granularity for your dashboard. Key-level is the right one.&lt;/p&gt;

&lt;p&gt;So the probe now records three things per attempt — status, response body, timestamp — and stores them per key. The catalog line stays in the log as context, never as a verdict. Reread against the 09.10.2026 run, the same data that made me want to write off four providers says something narrower: one is healthy, two are merely busy, one wants a different model ID, and one needs a top-up. Only one of those is a reason to remove anything.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>devops</category>
    </item>
    <item>
      <title>The fallback chain that silently stops working</title>
      <dc:creator>Cleo Cliona</dc:creator>
      <pubDate>Sun, 18 Oct 2026 16:49:58 +0000</pubDate>
      <link>https://dev.to/cleo_cliona/the-fallback-chain-that-silently-stops-working-5c8k</link>
      <guid>https://dev.to/cleo_cliona/the-fallback-chain-that-silently-stops-working-5c8k</guid>
      <description>&lt;h1&gt;
  
  
  The fallback chain that silently stops working
&lt;/h1&gt;

&lt;p&gt;I found out my fallback chain was broken because of a bill. Not an alert, not a red row in a dashboard — the spend for that week came out lower than usual in a way that felt wrong, and when I went looking, every request had been answered by the last model in the chain. The app was fine. Latency was tolerable. Nobody complained. I had been running on the emergency route for a while and calling it production.&lt;/p&gt;

&lt;p&gt;The first route in the chain had been retired. What stayed with me afterwards was how loudly that should have failed. A retired OpenAI model answers with HTTP 404 and &lt;code&gt;not_found_error&lt;/code&gt; / &lt;code&gt;model_not_found&lt;/code&gt;. That is a hard refusal, not a wobbly timeout. My chain caught that refusal, fell through to the next route, and kept going — which is precisely what I built it to do. By the time the error reached me it had already been swallowed and re-labeled as normal traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The chain hides the failure it was built to survive
&lt;/h2&gt;

&lt;p&gt;A fallback chain is a bet that a failing route is a temporary event. When that bet is right, you get a short blip and a slightly different answer. When the route is gone for good, the chain still does the right-looking thing: it serves the request from whatever is left. A chain with one surviving member is a green system. It is also a system you no longer understand, because the thing that broke is the thing that was supposed to protect you from breakage.&lt;/p&gt;

&lt;p&gt;The uncomfortable part is that a fallback which fires for a good reason and a fallback which fires because your primary route was deleted look identical from the outside. Both produce a successful response. The only difference is which model wrote it, and unless you log that, you have no signal at all. I didn't log it. So for a while my only instrument was the invoice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retirement is announced, if you read the field that carries it
&lt;/h2&gt;

&lt;p&gt;Retirement is not a mystery; it is data you have to go and fetch. OpenAI's model list exposes the schedule: &lt;code&gt;GET /v1/models&lt;/code&gt; and the single-model "Retrieve a model" call carry an optional &lt;code&gt;shutdown_date&lt;/code&gt; field, either a &lt;code&gt;YYYY-MM-DD&lt;/code&gt; or &lt;code&gt;null&lt;/code&gt;. The published notice periods are generous on paper — generally available models get six months or more, specialized variants at least three months, preview models sometimes only about two weeks — and fine-tunes follow the shutdown schedule of the base model they were trained from. That last clause is the one that bites, because your fine-tune keeps its own name and inherits someone else's deadline.&lt;/p&gt;

&lt;p&gt;Anthropic frames the same thing as a lifecycle: Active, Legacy, Deprecated, Retired, with the plain statement that requests to retired models will fail. There, the guarantee is a minimum notice window that applies to customers with active deployments. Read that qualifier carefully. If your traffic to a model has gone quiet — because a fallback quietly took over, say — you are much less of an active deployment than you think you are, and the announcement you were counting on may not arrive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The quieter breakages: parameters, credits, caps
&lt;/h2&gt;

&lt;p&gt;Retirement is only the loudest way a route dies. Two others cut a route without ever saying the word "deprecated". The first is the parameter surface. On Claude 4.7 and later, passing &lt;code&gt;temperature&lt;/code&gt;, &lt;code&gt;top_p&lt;/code&gt; or &lt;code&gt;top_k&lt;/code&gt; with a non-default value produces HTTP 400. My request builder had been sending a temperature for years because it once made the output nicer; on a newer model that same habit stops the call dead, and if it sits behind a fallback, the route is simply gone.&lt;/p&gt;

&lt;p&gt;The second is credit and cap accounting. Anthropic's usage credits expire one calendar year after they are issued, are not refundable, and the expiry date cannot be extended; closing the account destroys whatever balance is left. On the traffic side, the error shapes matter as much as the messages. A &lt;code&gt;rate_limit_error&lt;/code&gt; at 429 is also what you get when a tier spend cap is reached — with no &lt;code&gt;retry-after&lt;/code&gt;, and it stays that way until access comes back. Self-set spend limits surface as 400. A billing problem arrives as 402 &lt;code&gt;billing_error&lt;/code&gt;. None of these are "the model is gone", and all of them can take a route out of service for longer than a retired model would.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ping every route on its own, at max_tokens=1
&lt;/h2&gt;

&lt;p&gt;The test that would have caught my outage is almost insultingly small: address each route directly, one at a time, with &lt;code&gt;max_tokens=1&lt;/code&gt;, and never through the chain. One token is enough to get an HTTP status and an error body, and it is cheap enough to run on a schedule without thinking about it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/route.json &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'%{http_code}\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://api.openai.com/v1/chat/completions &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="s1"&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="s1"&gt;'{"model":"&amp;lt;the exact model string this chain entry uses&amp;gt;","max_tokens":1,"messages":[{"role":"user","content":"ping"}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point is what you do with the answer. If the body carries &lt;code&gt;not_found_error&lt;/code&gt; / &lt;code&gt;model_not_found&lt;/code&gt;, the route is not flaky, it is gone, and no amount of retry logic will bring it back. If it is a 400 on a parameter you thought was harmless, that is a code fix, not an incident. If it is 429 or 402, the route is fine and your wallet is the problem. Three different illnesses, three different responses — and a chain that answers every one of them the same way is not a monitoring strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't trust the monitor; compare against the live map
&lt;/h2&gt;

&lt;p&gt;My uptime check was pointed at the chain. That was the mistake. A health check that goes through a chain with one live route is green by construction, forever, which means it measures the chain's willingness to answer rather than the health of any route inside it. I had a monitor that could never fail and I read it every morning.&lt;/p&gt;

&lt;p&gt;What replaced it is duller. The chain lives in a file as data, not as nested &lt;code&gt;if&lt;/code&gt; branches. A job fetches the provider's model list, reads &lt;code&gt;shutdown_date&lt;/code&gt;, and diffs that list against the chain. Any chain entry whose name no longer appears, or whose shutdown date has passed, gets flagged before a request ever touches it. Anything running traffic that Anthropic has marked deprecated is findable the same way the vendor suggests you find it: Console → Usage → Export, the CSV broken out by API key and model, which shows which deprecated models are still being called and by which key. I also log which route actually answered each request, because the shift from "primary" to "last resort" is a symptom, and it is invisible in every dashboard that only counts successes.&lt;/p&gt;

&lt;p&gt;The number that finally told me something was wrong was the bill. I would rather it be the diff next time.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>devops</category>
    </item>
    <item>
      <title>7 things to check before you blame the model</title>
      <dc:creator>Cleo Cliona</dc:creator>
      <pubDate>Thu, 15 Oct 2026 16:41:48 +0000</pubDate>
      <link>https://dev.to/cleo_cliona/7-things-to-check-before-you-blame-the-model-45cg</link>
      <guid>https://dev.to/cleo_cliona/7-things-to-check-before-you-blame-the-model-45cg</guid>
      <description>&lt;h1&gt;
  
  
  7 things to check before you blame the model
&lt;/h1&gt;

&lt;p&gt;I spent an entire evening last month watching an agent crawl through a task at a pace that felt personal. The model kept returning half-finished answers, truncating tool calls, and generally behaving like it had given up on life. I was ready to switch providers. Then I checked the dashboard and discovered the job had been silently retrying against a dead route for two hours. The model was fine. My plumbing wasn't.&lt;/p&gt;

&lt;p&gt;Here's the order I work through now, cheapest checks first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is the job even running?
&lt;/h2&gt;

&lt;p&gt;This sounds insulting. It isn't. I once spent forty minutes convinced a model was hallucinating permissions errors, when in fact my worker had crashed on boot and the queue was red but nothing was consuming it. A dead job looks exactly like a stuck job from the outside.&lt;/p&gt;

&lt;p&gt;Check whether your agent process is actually alive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ps aux | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"worker|agent"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then look at your queue depth. If messages are piling up with no consumer attached, your problem is upstream of any model. Fix the supervisor, restart the worker, and the "broken model" starts working again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is the route alive?
&lt;/h2&gt;

&lt;p&gt;Your agent talks to a model through some path — an API endpoint, a proxy, a self-hosted endpoint behind a load balancer. That path can be down while the model itself is perfectly healthy. I've seen a DNS record expire on a Friday night and spend half of Saturday debugging "degraded model output" that was actually a connection timeout being retried with exponential backoff.&lt;/p&gt;

&lt;p&gt;Hit the endpoint directly, the same way your agent does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code} %{time_total}s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://your-endpoint/v1/models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 200 means the route is alive and the problem is downstream. A 502 or a timeout means you've found your culprit before you ever touch the model config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is the chain still the current one?
&lt;/h2&gt;

&lt;p&gt;Agents are pipelines. Output from step one becomes input to step two. If something upstream changes its format — a tool returns JSON where it used to return plain text, a schema gets an extra required field — the next step in the chain starts choking. The model isn't wrong. It's being fed something it wasn't expecting.&lt;/p&gt;

&lt;p&gt;I learned this when I updated a function-calling wrapper and didn't update the system prompt that described the output format. The model spent three cycles trying to parse responses that no longer matched what it had been told to expect. The fix was one line in a prompt file.&lt;/p&gt;

&lt;p&gt;When output looks garbled, diff the actual payloads between steps. Not what you think is being sent — what is being sent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did a context rebuild eat the budget?
&lt;/h2&gt;

&lt;p&gt;This one bit me hard. An agent will sometimes rebuild its context mid-task — re-summarize earlier turns, re-fetch system instructions, reassemble the tool definitions. If you're on a token-capped plan, that rebuild can consume your remaining budget in one shot. The model then starts truncating because it has no room left.&lt;/p&gt;

&lt;p&gt;If a job was working fine and suddenly the responses get shorter and dumber, check your token ledger:&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;# hypothetical — your tracker will differ&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"context_rebuild"&lt;/span&gt; jobs.log | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a rebuild fired, look at what triggered it. A context window limit hit, a manual reset, an automatic summarization threshold. Once you know the trigger, you can either raise the cap or pre-empt the rebuild by splitting the job into smaller pieces that fit without one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is the source throttled or empty?
&lt;/h2&gt;

&lt;p&gt;Your agent pulls data — files, APIs, documents, whatever. If that source starts returning 429 (rate limited) or 402 (payment required, for metered APIs), your agent sits there waiting on input that will never come. Then the model times out or produces garbage because it's trying to work with nothing.&lt;/p&gt;

&lt;p&gt;Watch the source's response codes:&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;# what is your fetch layer actually getting?&lt;/span&gt;
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; agent.log | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"429|402|503"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 429 means you're being throttled — back off, cache more aggressively, or add a second key. A 402 on a metered source means the bill wasn't paid or the quota was hit. Neither is a model problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is the primary path throttled too?
&lt;/h2&gt;

&lt;p&gt;Sometimes the source is fine but the path between your agent and that source is degraded. A rate-limited proxy, a VPN that's congested, a Cloudflare rule that's suddenly challenging your requests. The agent can't reach what it needs, so it stalls, and you blame the model for being slow.&lt;/p&gt;

&lt;p&gt;I run a simple health check that pings the full path — not just the endpoint, but the agent's actual route through whatever proxy or tunnel it uses:&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;# time a full round trip through your agent's route&lt;/span&gt;
&lt;span class="nb"&gt;time &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://your-proxy/source-endpoint &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;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"query":"ping"}'&lt;/span&gt; | jq &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the round trip is slow or failing while the direct endpoint is fast, you've found a routing issue. The model is waiting on a network problem, not thinking slowly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then, and only then, the model
&lt;/h2&gt;

&lt;p&gt;I've talked myself out of blaming the model six times out of ten. The model gets the blame when the job is running, the route is alive, the chain is current, no rebuild has eaten the budget, the source is returning real data, and the path to it is fast. If all of that checks out and the output is still broken — inconsistent reasoning, repeated failures on the same task, refusal to use tools correctly — then you can look at whether the model itself is the bottleneck.&lt;/p&gt;

&lt;p&gt;That last step is the one I used to start with. It's the hardest to diagnose, the most expensive to fix, and the least likely to be the actual problem.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>devops</category>
    </item>
    <item>
      <title>The CHEAPEST DeepSeek V4.1 Flash Route Right Now (October 2026)</title>
      <dc:creator>Cleo Cliona</dc:creator>
      <pubDate>Sun, 11 Oct 2026 04:32:25 +0000</pubDate>
      <link>https://dev.to/cleo_cliona/the-cheapest-deepseek-v41-flash-route-right-now-october-2026-5da2</link>
      <guid>https://dev.to/cleo_cliona/the-cheapest-deepseek-v41-flash-route-right-now-october-2026-5da2</guid>
      <description>&lt;h1&gt;
  
  
  The CHEAPEST DeepSeek V4.1 Flash Route Right Now (October 2026)
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Last updated: October 11, 2026&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Running AI agents? Then you know the drill: API bills that spiral, rate limits that choke your workflow, and the endless hunt for a model that's actually smart AND affordable.&lt;/p&gt;

&lt;p&gt;I tested &lt;strong&gt;dozens of providers&lt;/strong&gt; running DeepSeek V4.1 Flash. Here's the cheapest route that actually holds up in production — and the free alternatives worth your time.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Winner: TokenRa — $0.09/M Input, $0.36/M Output
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Input $/M&lt;/th&gt;
&lt;th&gt;Output $/M&lt;/th&gt;
&lt;th&gt;Cache Hit&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;&lt;strong&gt;TokenRa&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.09&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.36&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0.0018&lt;/td&gt;
&lt;td&gt;🏆 Cheapest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SiliconFlow&lt;/td&gt;
&lt;td&gt;$0.14&lt;/td&gt;
&lt;td&gt;$0.28&lt;/td&gt;
&lt;td&gt;$0.0028&lt;/td&gt;
&lt;td&gt;Good, but no free tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek direct (off-peak)&lt;/td&gt;
&lt;td&gt;$0.15&lt;/td&gt;
&lt;td&gt;$0.60&lt;/td&gt;
&lt;td&gt;$0.003&lt;/td&gt;
&lt;td&gt;Solid fallback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nous Portal&lt;/td&gt;
&lt;td&gt;$0.15&lt;/td&gt;
&lt;td&gt;$0.60&lt;/td&gt;
&lt;td&gt;$0.0029&lt;/td&gt;
&lt;td&gt;Reseller, same price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenRouter&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$0.80&lt;/td&gt;
&lt;td&gt;$0.004&lt;/td&gt;
&lt;td&gt;Convenient, pricier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Baidu&lt;/td&gt;
&lt;td&gt;$0.561&lt;/td&gt;
&lt;td&gt;$1.76&lt;/td&gt;
&lt;td&gt;$0.104&lt;/td&gt;
&lt;td&gt;❌ Way overpriced&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That $0.09 rate has a name: the model ID &lt;strong&gt;&lt;code&gt;deepseek-v4.1-flash-50off&lt;/code&gt;&lt;/strong&gt;. TokenRa also sells a plain &lt;code&gt;deepseek-v4.1-flash&lt;/code&gt; — same model, $0.37 in / $1.49 out, four times the price. The cheap tier is the one to call explicitly; the alias is the trap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TokenRa is ~40% cheaper than DeepSeek direct&lt;/strong&gt; and ~60% cheaper than OpenRouter. Per typical agent turn (10k input + 2k output tokens):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TokenRa: $0.0016/turn&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;DeepSeek direct (off-peak): $0.0027/turn&lt;/li&gt;
&lt;li&gt;OpenRouter: $0.0028/turn&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At 50 turns/day, that's &lt;strong&gt;$0.08 vs $0.13 vs $0.14&lt;/strong&gt; - and it adds up to real money over a month.&lt;/p&gt;




&lt;h2&gt;
  
  
  Free Models on TokenRa (Yes, Really)
&lt;/h2&gt;

&lt;p&gt;TokenRa also runs &lt;strong&gt;$0/month models&lt;/strong&gt; — with a caveat:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Context&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Union Alpha&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;262K, multimodal&lt;/td&gt;
&lt;td&gt;~17s P50, ~14 tok/s&lt;/td&gt;
&lt;td&gt;Batch jobs only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ox Alpha&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;1M, multimodal&lt;/td&gt;
&lt;td&gt;~17s P50, ~14 tok/s&lt;/td&gt;
&lt;td&gt;Batch jobs only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Space Bunny Alpha&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Experimental&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Reality check:&lt;/strong&gt; These run on shared capacity. 17-second latency kills interactive use — but for overnight batch jobs, data labeling, or non-urgent summarization? Perfect. Note that these free models still require a funded account — an empty balance returns a quota error even for a $0 model.&lt;/p&gt;




&lt;h2&gt;
  
  
  What About Free Alternatives?
&lt;/h2&gt;

&lt;p&gt;I also stress-tested the major free-tier providers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Free Model&lt;/th&gt;
&lt;th&gt;Tool Calling&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;&lt;strong&gt;tokenharbor.ai&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;deepseek-v4.1-flash:free&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;🏆 Best free route&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nous&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;longcat-2.5-preview:free&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;Solid backup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenRouter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Various :free (rotating)&lt;/td&gt;
&lt;td&gt;Mixed&lt;/td&gt;
&lt;td&gt;Unreliable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UnoRouter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;81 free models&lt;/td&gt;
&lt;td&gt;❌ 1 req/min&lt;/td&gt;
&lt;td&gt;Too slow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Gemini&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;gemini-flash-latest&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;Good, rate-limited&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;tokenharbor.ai&lt;/strong&gt; is the free-route king right now: deepseek-v4.1-flash with full tool calling, 1M context, zero cost. But it's prepaid — when credits run out, you're done until you top up.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Verdict
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For production AI agents on a budget:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Primary: TokenRa&lt;/strong&gt; — $0.09/M input, cheapest reliable route, full tool calling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup: tokenharbor.ai&lt;/strong&gt; — free tier when credits last, $0.15/M when they don't&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch jobs: Union Alpha (TokenRa)&lt;/strong&gt; — completely free, just slow&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;For hobby projects:&lt;/strong&gt; Stick with tokenharbor.ai's free tier. Genuinely free, tool calling, 1M context. Just don't build production on it — credits can vanish without warning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;👉 &lt;strong&gt;Sign up for TokenRa:&lt;/strong&gt; &lt;a href="https://tokenra.io/sign-up?aff=wMoD" rel="noopener noreferrer"&gt;tokenra.io/sign-up?aff=wMoD&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My referral link doesn't cost you a cent extra — it just helps me keep testing routes so you don't have to.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Prices checked October 11, 2026. Free-tier availability changes frequently — always verify current rates in the provider console.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>devops</category>
    </item>
    <item>
      <title>What your agent actually costs per task</title>
      <dc:creator>Cleo Cliona</dc:creator>
      <pubDate>Fri, 09 Oct 2026 16:40:36 +0000</pubDate>
      <link>https://dev.to/cleo_cliona/what-your-agent-actually-costs-per-task-1b7i</link>
      <guid>https://dev.to/cleo_cliona/what-your-agent-actually-costs-per-task-1b7i</guid>
      <description>&lt;h1&gt;
  
  
  What your agent actually costs per task
&lt;/h1&gt;

&lt;p&gt;I started tracking my agent's costs the way most people do: I looked at the token price, multiplied it by the tokens I used, and called it a day. It took me embarrassingly long to realize that number told me almost nothing about whether my agent was actually cheap to run.&lt;/p&gt;

&lt;p&gt;The problem with token price is that it's a unit cost, not a task cost. A model can be cheap per token and still expensive per completed task if it needs many turns to get there. Or it can be expensive per token and cheap per task if it nails something immediately. When I only looked at the per-token rate, I was optimizing for the wrong thing entirely. I was comparing the price of flour when what I cared about was the price of the bread.&lt;/p&gt;

&lt;p&gt;What I started tracking instead was cost per turn, and more importantly, cost per completed task. Not cost per token, not cost per session — cost per task that actually finished with a usable result. The difference matters more than I expected, and it's not even close.&lt;/p&gt;

&lt;p&gt;Here's what that looks like in practice. After each agent session, I run a small script that logs a few things: how many turns the agent took, how many tool calls it made, whether the final output was something I could actually use, and the total tokens consumed. That last number I still record, but it's the least interesting column. The column I actually look at is the one that says whether the task succeeded or not.&lt;/p&gt;

&lt;p&gt;Because here's the thing I learned the hard way: a run that burns its entire budget and produces nothing still costs the full budget. I don't get a refund for the agent going in circles, re-reading the same file repeatedly, or calling a tool with the wrong arguments over and over until it hits the limit. From a cost perspective, a failed run and a successful run can look identical. The only difference is one of them gave me something I could use.&lt;/p&gt;

&lt;p&gt;I remember a session where the agent spent the entire budget trying to debug a configuration file. It kept making the same edit, running the same test, getting the same error, and trying a variation of the same edit. The output was useless. But the tokens it consumed while doing that weren't free just because the result was garbage. They were the most expensive tokens of all, because they bought nothing. That session cost the same as a session that actually fixed the problem, but it delivered none of the value.&lt;/p&gt;

&lt;p&gt;This changed how I think about debugging agent behavior. When a task fails, I don't just see a wasted output — I see a wasted input too. All those tokens the agent consumed while flailing weren't free just because the result was garbage. They were the most expensive tokens of all, because they bought nothing. I started asking different questions: not "how many tokens did this use" but "how many tokens did this use before it went wrong."&lt;/p&gt;

&lt;p&gt;The other observation that surprised me: routing matters more than model choice. I used to think the big decision was which model to send a task to. I'd agonize over whether a task needed the biggest model or if a smaller one would do. But what I found is that how I route a task — whether I send it to a single model, whether I break it into sub-tasks, whether I let the agent decide its own approach or constrain it upfront — that changes the outcome more than swapping one model for another at the same capability level.&lt;/p&gt;

&lt;p&gt;A well-routed task to a mid-tier model often beats a poorly-routed task to a top-tier model. The routing determines how many turns the task will take, how many dead ends the agent will explore, and whether it even understands what I'm asking for. The model determines how well it executes once the routing is right. Get the routing wrong and even the best model will burn budget wandering.&lt;/p&gt;

&lt;p&gt;I've seen this play out repeatedly. A task that I send to a capable model with a vague instruction comes back half-finished and over budget. The same task, broken into clear sub-tasks with explicit constraints, finishes quickly on a smaller model. The model didn't change. The routing did. And the routed version cost less not because the tokens were cheaper but because there were fewer of them — fewer turns, fewer retries, fewer dead ends.&lt;/p&gt;

&lt;p&gt;I don't have a dashboard full of pretty charts. I have a log file and a habit of reading it. But that log file told me more about my agent's real costs than any per-token price ever did. The number that matters isn't what a token costs. It's what a completed task costs — and a task that doesn't complete still costs something.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/writing/"&gt;← Cleo's Blog&lt;/a&gt; · &lt;a href="https://dev.to/"&gt;Cleo's Nine&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>devops</category>
    </item>
    <item>
      <title>I re-probed every free LLM API I could get a key for: what's actually alive</title>
      <dc:creator>Cleo Cliona</dc:creator>
      <pubDate>Fri, 09 Oct 2026 16:11:06 +0000</pubDate>
      <link>https://dev.to/cleo_cliona/i-re-probed-every-free-llm-api-i-could-get-a-key-for-whats-actually-alive-4cb7</link>
      <guid>https://dev.to/cleo_cliona/i-re-probed-every-free-llm-api-i-could-get-a-key-for-whats-actually-alive-4cb7</guid>
      <description>&lt;h1&gt;
  
  
  I re-probed every free LLM API I could get a key for: what's actually alive
&lt;/h1&gt;

&lt;p&gt;Free-tier listicles go stale within weeks. Model IDs retire, quotas change, and the article that told you "Provider X is free" still ranks on page one a year later.&lt;/p&gt;

&lt;p&gt;So I did the thing I keep telling other people to do: I stopped trusting the lists and re-probed every provider I hold a key for. Here is what came back, dated &lt;strong&gt;October 2026&lt;/strong&gt;, with the method spelled out so you can re-run it yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The method matters more than the list
&lt;/h2&gt;

&lt;p&gt;One rule up front, because it's the trap everyone falls into:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A &lt;code&gt;200&lt;/code&gt; from &lt;code&gt;/v1/models&lt;/code&gt; proves your key parses. It proves nothing about generation.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have providers that list 60+ models happily and then refuse every single chat call with &lt;code&gt;429&lt;/code&gt;. A catalogue endpoint is a metadata endpoint. The only thing that counts as "usable" is a real one-token completion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&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;"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="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&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="s1"&gt;'{"model":"MODEL_ID","messages":[{"role":"user","content":"ping"}],"max_tokens":1}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://provider.example/v1/chat/completions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run that, then read the status code as a sentence — not as a pass/fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the probes actually returned
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Catalogue&lt;/th&gt;
&lt;th&gt;One-token chat&lt;/th&gt;
&lt;th&gt;Reading&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Groq&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;11 models&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;200&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Alive.&lt;/strong&gt; &lt;code&gt;gpt-oss-20b&lt;/code&gt; and &lt;code&gt;qwen3.8-27b&lt;/code&gt; both answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cerebras&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;free tier&lt;/td&gt;
&lt;td&gt;&lt;code&gt;200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Alive, ~1M tokens/day on the free plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenRouter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;14 free slugs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Alive — 20 rpm, 50/day free (1000/day after a one-time $10)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Token Harbor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;:free&lt;/code&gt; slugs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;200&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Alive, free IDs "are never billed"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Gemini&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;62 models&lt;/td&gt;
&lt;td&gt;&lt;code&gt;429&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Key fine, &lt;strong&gt;quota/region refused&lt;/strong&gt; — not a wiring bug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mistral&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;46 models&lt;/td&gt;
&lt;td&gt;&lt;code&gt;429&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Key fine, free-mode credit spent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NVIDIA NIM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;80 models&lt;/td&gt;
&lt;td&gt;&lt;code&gt;410 Gone&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Catalogue is real; the IDs I tried were retired&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SambaNova&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6 models&lt;/td&gt;
&lt;td&gt;&lt;code&gt;402&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"balance_units: 0" — no free credit on this account&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three of those eight would look "broken" if I only checked the catalogue. Gemini and Mistral would look broken even with a correct one-token probe — &lt;code&gt;429&lt;/code&gt; means &lt;em&gt;the credential works and the tier is throttled&lt;/em&gt;, which is a completely different action from "wrong key".&lt;/p&gt;

&lt;h2&gt;
  
  
  The three findings worth keeping
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. A retired model ID is not a retired provider.&lt;/strong&gt; I wrote Groq off months ago because &lt;code&gt;llama-3.3-70b-versatile&lt;/code&gt; returned &lt;code&gt;404&lt;/code&gt;. I repeated that as fact. It was wrong: the endpoint was fine the whole time, the model name had rotated. If you have a note in your own docs saying "X is dead", re-probe before you act on it — including your own notes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Account-level free tiers have no &lt;code&gt;:free&lt;/code&gt; suffix.&lt;/strong&gt; Aggregators name their free models &lt;code&gt;something:free&lt;/code&gt;, which makes them easy to detect. First-party free tiers — Groq, Cerebras, Gemini, NVIDIA, Mistral, Cloudflare — are free &lt;em&gt;by account&lt;/em&gt;, not by model name, and often report no pricing at all. Any filter of the form &lt;code&gt;if ":free" in model_id&lt;/code&gt; silently discards all of them. I ran that filter for weeks and was ranking a fraction of the capacity I actually had.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;402&lt;/code&gt; and &lt;code&gt;429&lt;/code&gt; are the two most misread codes in this space.&lt;/strong&gt; &lt;code&gt;402&lt;/code&gt; means the account has no credit — the key is perfect, the account is empty. &lt;code&gt;429&lt;/code&gt; means the key works and the tier is throttled &lt;em&gt;right now&lt;/em&gt;. Neither is a wiring problem, and neither is fixed by re-issuing a key.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to keep this honest over time
&lt;/h2&gt;

&lt;p&gt;A dated table is a snapshot; a snapshot becomes a lie the moment it's quoted without its date. So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Probe on a schedule, not on memory.&lt;/strong&gt; Mine runs every two hours and rewrites the route map — it costs nothing and calls no model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish the date.&lt;/strong&gt; "Free in October 2026" is useful. "Free" is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Probe the negative too.&lt;/strong&gt; A monitor that reports something &lt;em&gt;disappeared&lt;/em&gt; is reporting an absence, and absences are exactly where tooling lies. I once watched my own cap drop a perfectly live route while a dead one stayed in — and the report blamed the provider.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If you want the safety net
&lt;/h2&gt;

&lt;p&gt;Free tiers will sometimes all be exhausted at once, and that is a state, not a bug — a rolling window resets and the agent resumes. What I do is put exactly one paid leg at the very end of the chain so it only ever fires when everything free is spent, and keep it in a single account that covers both the agent runtime and the API — the &lt;a href="https://cleosnine.com/r" rel="noopener noreferrer"&gt;Nous Portal&lt;/a&gt; (200+ models, hosted tools, monthly credits, high rate limits) is the one I settled on.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;That's my referral link: $15 off the first month ($20 → $5) for new customers on a new personal subscription, and I get a credit if you use it. Everything above is measured, not sponsored.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Re-run the probe yourself
&lt;/h2&gt;

&lt;p&gt;The whole survey above is one loop over providers holding a key, one &lt;code&gt;/v1/models&lt;/code&gt; fetch to build the candidate list, and one &lt;code&gt;max_tokens: 1&lt;/code&gt; chat call per candidate. That's it — no SDK, no framework, no cost. Do it before you trust any list, including this one.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
      <category>devops</category>
    </item>
    <item>
      <title>Running an AI agent on free API tiers: 7 things that actually work</title>
      <dc:creator>Cleo Cliona</dc:creator>
      <pubDate>Fri, 09 Oct 2026 15:43:36 +0000</pubDate>
      <link>https://dev.to/cleo_cliona/running-an-ai-agent-on-free-api-tiers-7-things-that-actually-work-5g6b</link>
      <guid>https://dev.to/cleo_cliona/running-an-ai-agent-on-free-api-tiers-7-things-that-actually-work-5g6b</guid>
      <description>&lt;h1&gt;
  
  
  Running an AI agent on free API tiers: 7 things that actually work
&lt;/h1&gt;

&lt;p&gt;I run an agent that does real work every day — writing, proofreading, researching, running scheduled jobs — and I try hard not to pay for the models. Not because free models are as good as paid ones (they often aren't), but because the difference between "$30/month" and "$0" is the difference between a hobby and a habit.&lt;/p&gt;

&lt;p&gt;Here's the setup that survived contact with reality, and the seven things I got wrong on the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup: three layers
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A route scout&lt;/strong&gt; — a script that probes every free route across the providers I hold keys for, tests whether each one can actually do tool calling, ranks them, and writes a map. It runs every two hours, costs nothing, and calls no LLM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A chain builder&lt;/strong&gt; — reads that map and writes the fallback chain: best live free route first, then the next, … and paid legs only at the very end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A failover hook&lt;/strong&gt; — a small plugin that classifies "model retired / free offer ended" as &lt;em&gt;do not retry&lt;/em&gt;, so the chain advances immediately instead of burning twelve retries with backoff on a route that is never coming back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything below is a lesson from building that.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Free tiers rotate weekly — never hard-code a model
&lt;/h2&gt;

&lt;p&gt;Published free-tier lists are stale within weeks. The model you carefully wired in last month is retired today, and the listicle that recommended it still ranks on Google.&lt;/p&gt;

&lt;p&gt;The consequence is architectural: the chain must be built from a &lt;strong&gt;live measurement&lt;/strong&gt;, not from a curated list. That's the entire reason layer 1 exists. If your fallback list is a constant in your config, it is already outdated.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. "Provider X is dead" ages badly
&lt;/h2&gt;

&lt;p&gt;I wrote off Groq months ago because the model ID I was using returned &lt;code&gt;404&lt;/code&gt;. That conclusion sat in my notes as fact, and I repeated it to myself.&lt;/p&gt;

&lt;p&gt;I re-probed it this week: &lt;code&gt;200 OK&lt;/code&gt;. The endpoint was fine the whole time — the model ID had simply rotated. A retired &lt;strong&gt;model&lt;/strong&gt; is not a retired &lt;strong&gt;provider&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now I re-probe before believing any "X is dead" note, including my own. It takes one API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&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;"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="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&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="s1"&gt;'{"model":"your-model","messages":[{"role":"user","content":"ping"}],"max_tokens":1}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://api.example.com/v1/chat/completions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;200&lt;/code&gt; from &lt;code&gt;/v1/models&lt;/code&gt; proves your key parses. It does not prove you can generate. Only a one-token completion does that.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;code&gt;":free"&lt;/code&gt; is not a property of free tiers
&lt;/h2&gt;

&lt;p&gt;This one cost me real capacity. My scout decided a model was free 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="n"&gt;is_free&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;completion_price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_free&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:free&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aggregators name their free models &lt;code&gt;something:free&lt;/code&gt;. But &lt;strong&gt;first-party free tiers don't&lt;/strong&gt;: Groq, Cerebras, Gemini, NVIDIA's catalog, Mistral, Cloudflare — their free models are free &lt;em&gt;by account&lt;/em&gt;, and many models don't report pricing at all. Under that rule they were invisible even though I held valid keys for them. My scout was ranking a fraction of the capacity I had.&lt;/p&gt;

&lt;p&gt;The fix is a per-provider flag, not a smarter string match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;FREE_TIER_PROVIDERS&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;groq&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;cerebras&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;gemini&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;nvidia&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;mistral&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;cloudflare&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;is_free&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_price&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;completion_price&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="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;FREE_TIER_PROVIDERS&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;prompt_price&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test your assumption the same way I found mine: take a provider you &lt;em&gt;know&lt;/em&gt; has a free tier, and check whether your scout ever reports any of its models. If not, your filter is the bug — not the provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Never truncate an aggregator's catalogue
&lt;/h2&gt;

&lt;p&gt;I added a per-provider cap to stop one provider flooding the ranking. Sensible in theory. In practice I applied it to &lt;em&gt;every&lt;/em&gt; provider — and cut an aggregator from &lt;strong&gt;80 free models to 8&lt;/strong&gt;, while dropping a &lt;strong&gt;live&lt;/strong&gt; route from the map because it happened to be entry number nine.&lt;/p&gt;

&lt;p&gt;Meanwhile a &lt;em&gt;dead&lt;/em&gt; route (the paid-only successor) stayed in, because it happened to sort earlier.&lt;/p&gt;

&lt;p&gt;Two rules came out of that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scope a cap to the providers that need it. Aggregators with genuinely large free catalogues should not be capped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A cap must never be the thing that decides whether a route exists.&lt;/strong&gt; Sort by what you actually care about (liveness, quality), then cap.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Verify "disappeared" with a live probe
&lt;/h2&gt;

&lt;p&gt;My scout printed that a route had vanished from the free list. My first instinct was to believe it and rebuild around the loss.&lt;/p&gt;

&lt;p&gt;The model answered &lt;code&gt;200 OK&lt;/code&gt; when I probed it directly. The route hadn't disappeared — my own cap had dropped it, and the tool reported its own bug as an upstream change.&lt;/p&gt;

&lt;p&gt;Any monitor that reports &lt;em&gt;absence&lt;/em&gt; is reporting a negative, and negatives are exactly where tools lie. Absence claims deserve the same scepticism as success claims.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Rank by quality, not by speed
&lt;/h2&gt;

&lt;p&gt;Early on my ranking rewarded liveness and latency. The winner was a model that answered in 0.5 seconds — and was useless for agent loops. It went into position 1 of the chain and every turn paid for it.&lt;/p&gt;

&lt;p&gt;The fix: a &lt;strong&gt;quality axis&lt;/strong&gt;. I fetch a public model-quality table, normalise names, and score it into the ranking with a weight large enough that a good, live, free model beats a fast, dumb one. Crucially, models with &lt;strong&gt;no score entry must score neutral&lt;/strong&gt;, not zero:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;quality&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise a known-weak model (scored 0.13) outranks an unknown good one (no entry) purely for having a number. Which is exactly what happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Cap the failover budget, and make the reload drain-aware
&lt;/h2&gt;

&lt;p&gt;Two operational details that matter more than they look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retries per turn are a budget.&lt;/strong&gt; Every failover jump spends from it, and so does a context rebuild. Set it too low and the chain "gives up" after a few hops (&lt;code&gt;restart limit exceeded&lt;/code&gt;). Mine is 12.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reload gracefully.&lt;/strong&gt; If your chain is rebuilt on a schedule, use a drain-aware reload (SIGUSR1-style) rather than a restart. A restart kills in-flight turns; a drain-aware reload waits for the current one to finish. On a long-running job that difference is the entire job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When free isn't enough
&lt;/h2&gt;

&lt;p&gt;Free tiers will sometimes all be exhausted at once. That's what a degraded state looks like, not a bug: a rolling window resets, and the agent resumes. If you want a safety net, put &lt;strong&gt;exactly one paid leg at the very end&lt;/strong&gt; of the chain, so it fires only when everything free is spent.&lt;/p&gt;

&lt;p&gt;For me that's one subscription that covers both the agent and the API — the &lt;a href="https://cleosnine.com/r" rel="noopener noreferrer"&gt;Nous Portal&lt;/a&gt; (200+ models, hosted tools, monthly credits, high rate limits). Clearing the free-then-paid handoff in one account is worth more than the per-token price, because the expensive part of running an agent is not the token — it's the evening you spend re-wiring keys.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;That's my referral link: it takes $15 off the first month ($20 → $5) for new customers on a new personal subscription, and I get a credit if you use it. Everything above is the free setup I actually run.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Build the chain from a &lt;strong&gt;live probe&lt;/strong&gt;, not a list.&lt;/li&gt;
&lt;li&gt;Test free-ness by &lt;strong&gt;account&lt;/strong&gt;, not by model name.&lt;/li&gt;
&lt;li&gt;Never let a cap decide whether a route exists.&lt;/li&gt;
&lt;li&gt;Probe absences before acting on them.&lt;/li&gt;
&lt;li&gt;Score quality, and let unknown mean &lt;em&gt;neutral&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Budget retries per turn; reload drain-aware.&lt;/li&gt;
&lt;li&gt;One paid leg, last, disclosed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run an agent long enough, the model is the cheap part. The routing is the work.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>python</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
