<?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: Josh Green</title>
    <description>The latest articles on DEV Community by Josh Green (@josh_green_dev).</description>
    <link>https://dev.to/josh_green_dev</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3811550%2Fe3376e9e-5467-4ed6-a3f6-b4698a6721f0.png</url>
      <title>DEV Community: Josh Green</title>
      <link>https://dev.to/josh_green_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/josh_green_dev"/>
    <language>en</language>
    <item>
      <title>Free LLMs on OpenRouter Keep Going 404. I Fixed It With 120 Lines of Python</title>
      <dc:creator>Josh Green</dc:creator>
      <pubDate>Sun, 08 Mar 2026 17:54:08 +0000</pubDate>
      <link>https://dev.to/josh_green_dev/free-llms-on-openrouter-keep-going-404-i-fixed-it-with-120-lines-of-python-43i1</link>
      <guid>https://dev.to/josh_green_dev/free-llms-on-openrouter-keep-going-404-i-fixed-it-with-120-lines-of-python-43i1</guid>
      <description>&lt;p&gt;I built a small pipeline on OpenClaw to stay on top of 3D printing news.&lt;/p&gt;

&lt;p&gt;Nothing fancy — a Python script that pulls from YouTube, RSS feeds, and Reddit, uses a free LLM to summarize what's worth reading, and emails me a digest. I use OpenRouter's free tier because I'm cheap and the models are good enough for summarization.&lt;/p&gt;

&lt;p&gt;It worked great. For about two weeks.&lt;/p&gt;

&lt;p&gt;Then I started getting errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem nobody talks about
&lt;/h2&gt;

&lt;p&gt;Here's something I didn't fully appreciate until it bit me: free models on OpenRouter change constantly. Models get added, removed, rate-limited into uselessness, or quietly replaced with different versions. If you hardcode your model list — which every tutorial tells you to do — you're building on sand.&lt;/p&gt;

&lt;p&gt;One morning I woke up to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[06:03] LLM HTTP 404 [openai/gpt-oss-120b:free]: model not found
[06:03] LLM HTTP 429 [nousresearch/hermes-3-llama-3.1-405b:free]: rate limited
[06:03] LLM HTTP 404 [mistralai/mistral-small-3.1-24b-instruct:free]: model not found
[06:03] All free models exhausted — returning empty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Three of my six hardcoded models were dead. The pipeline silently produced nothing. I missed a week of content before I noticed.&lt;/p&gt;

&lt;p&gt;Hardcoded lists are technical debt. Free model availability is a moving target. These two facts collide badly.&lt;/p&gt;


&lt;h2&gt;
  
  
  The fix: treat the model list as a live data source
&lt;/h2&gt;

&lt;p&gt;OpenRouter has a public endpoint — no auth required — that returns their full model catalog:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET https://openrouter.ai/api/v1/models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It returns ~346 models right now. Filtering to free ones with decent context windows gives you 10-15 candidates. The question is: which ones are actually worth using?&lt;/p&gt;

&lt;p&gt;I wanted to rank them. My criteria:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context window&lt;/strong&gt; — longer is better for summarization. A 262K context model can swallow an entire article thread without chunking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model size&lt;/strong&gt; — bigger models write better. A 70B model beats a 7B model for prose quality.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical reliability&lt;/strong&gt; — has this model actually worked when I've called it before?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last one is the one nobody tracks. So I built tracking.&lt;/p&gt;


&lt;h2&gt;
  
  
  model-registry.py — the discovery layer
&lt;/h2&gt;

&lt;p&gt;The registry script runs once every 6 hours. It:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Checks if the cache (&lt;code&gt;~/.openclaw/free-models.json&lt;/code&gt;) is fresh — if yes, exits in &amp;lt;100ms (just a file stat)&lt;/li&gt;
&lt;li&gt;If stale, hits the OpenRouter catalog and scores every free model:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;score_model&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;span class="n"&gt;context_length&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;context_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context_length&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# caps at 200
&lt;/span&gt;    &lt;span class="n"&gt;size_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_size_score&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;span class="c1"&gt;# regex: 405b=200, 70b=140, 8b=50...
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;context_score&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;size_score&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Takes the top 10, writes them to &lt;code&gt;free-models.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Logs a diff — "Added: X, Removed: Y since last run"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The diff log is where it gets interesting. On my first run after building this, I discovered two models I'd never heard of that scored in my top 6. One of them — &lt;code&gt;qwen/qwen3-next-80b-a3b-instruct:free&lt;/code&gt; — has a 262K context window and an 80B parameter count. It's now my primary model. It wasn't in any tutorial I'd read.&lt;/p&gt;


&lt;h2&gt;
  
  
  model-metrics.py — the performance layer
&lt;/h2&gt;

&lt;p&gt;HTTP 200 doesn't mean the model was useful. A model can return 200 with three sentences of hallucinated nonsense that breaks your JSON parser downstream.&lt;/p&gt;

&lt;p&gt;So I added tracking at two levels:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Level 1 — HTTP success:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="nf"&gt;record_metric&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;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;latency_ms&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                 &lt;span class="n"&gt;output_len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;record_metric&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;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;latency_ms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;error_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Level 2 — parse success (&lt;code&gt;parse_ok&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After every call that expects structured JSON, I record whether the downstream parsing succeeded:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_free_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claim_extraction&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;update_parse_ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# output was actually usable
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;update_parse_ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# model returned garbage
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;parse_ok&lt;/code&gt; is the metric I care about most. It answers: was this model actually useful, not just technically responsive?&lt;/p&gt;

&lt;p&gt;After a week of pipeline runs, I get a table like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Model                                      calls  ok%  p_ok%  avg_ms  errors
meta-llama/llama-3.3-70b-instruct:free       47   94%   88%   1240ms
qwen/qwen3-next-80b-a3b-instruct:free        31   97%   91%   1180ms
openai/gpt-oss-120b:free                     12   58%   42%   1890ms  5×404
nousresearch/hermes-3-llama-3.1-405b:free    8    62%   55%   2100ms  3×404
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The last two models look fine on paper (they're large, they have long context) but they're dying constantly. Their scores get penalized:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;score_penalty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stats_entry&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stats_entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;   &lt;span class="c1"&gt;# heavy penalty
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;               &lt;span class="c1"&gt;# no penalty
&lt;/span&gt;
&lt;span class="n"&gt;final_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;catalog_score&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;score_penalty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;historical_stats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When the registry next refreshes, those models sink to the bottom of the fallback chain. Automatically. Without me touching anything.&lt;/p&gt;


&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;The pipeline now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discovers new free models within 6 hours of them appearing on OpenRouter&lt;/li&gt;
&lt;li&gt;Drops dead models from the rotation within one pipeline run&lt;/li&gt;
&lt;li&gt;Prioritizes models with proven parse reliability, not just raw specs&lt;/li&gt;
&lt;li&gt;Costs $0.00 extra — one public HTTP GET every 6 hours&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing is ~250 lines across two files. No pip dependencies for the registry itself (stdlib only — &lt;code&gt;json&lt;/code&gt;, &lt;code&gt;urllib&lt;/code&gt;, &lt;code&gt;sqlite3&lt;/code&gt;). The metrics use SQLite so they survive reboots and redeploys.&lt;/p&gt;


&lt;h2&gt;
  
  
  Grab the code
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;model-registry.py&lt;/code&gt; and &lt;code&gt;model-metrics.py&lt;/code&gt; — both standalone, drop them next to any script that calls OpenRouter:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Replace your hardcoded list with this:
&lt;/span&gt;&lt;span class="n"&gt;REGISTRY_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.openclaw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;free-models.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;_FALLBACK&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;meta-llama/llama-3.3-70b-instruct:free&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_free_models&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REGISTRY_PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;models&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_FALLBACK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;FREE_MODELS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_free_models&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Run the registry as a preflight step before any pipeline that uses free models. If the cache is fresh, it exits immediately. If it's stale, it updates in ~1 second.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 model-registry.py &lt;span class="nt"&gt;--max-age&lt;/span&gt; 21600   &lt;span class="c"&gt;# refresh if &amp;gt;6h old&lt;/span&gt;
python3 your-pipeline.py                     &lt;span class="c"&gt;# now uses fresh model list&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;The thing I keep thinking about: I built this to find 3D printing news: the RepRap machines that print their own parts. Then foraging for news made me realize I needed this algorithm. Now the algorithm helps me find better news about the Van Neuman probe itself. It's turtles all the way down — but at least they're free turtles.&lt;/p&gt;



&lt;p&gt;Full code on GitHub Gist: 

&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;





</description>
      <category>python</category>
      <category>llm</category>
      <category>tutorial</category>
      <category>openclaw</category>
    </item>
    <item>
      <title>Never Had Such a Good Grip on Funcional Prints Before</title>
      <dc:creator>Josh Green</dc:creator>
      <pubDate>Sat, 07 Mar 2026 12:47:50 +0000</pubDate>
      <link>https://dev.to/josh_green_dev/never-had-such-a-good-grip-on-funcional-prints-before-i-never-had-such-a-good-grip-on-funcional-1mnl</link>
      <guid>https://dev.to/josh_green_dev/never-had-such-a-good-grip-on-funcional-prints-before-i-never-had-such-a-good-grip-on-funcional-1mnl</guid>
      <description>&lt;p&gt;I bought my Bambu Lab P1S last year after moving to Budapest. Cheap rent, fast internet, and suddenly I had space for a hobby that wasn't just staring at VS Code for 14 hours a day.&lt;/p&gt;

&lt;p&gt;Everyone warned me I’d print "a few useful things then revert to benchies and baby Yodas." They weren’t entirely wrong, I definitely have a small army of calibration cubes I should throw out. But prints like the one I saw today remind me why I got into this in the first place.&lt;/p&gt;

&lt;p&gt;Someone &lt;a href="https://lemmy.world/post/43897576" rel="noopener noreferrer"&gt;designed and printed a custom hand control hoods backing&lt;/a&gt; for their road bike. Not a phone mount. Not a GoPro adapter. The actual rubberized part your palms rest on for hours during a ride.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem with Stock Parts&lt;/strong&gt;&lt;br&gt;
I spent three years doing Shopify theme development, and one thing that stuck with me: most products are designed for the mythical "average user." That works fine if you’re selling t-shirts. It doesn’t work when you’re talking about close contact human anatomy and high-performance equipment.&lt;/p&gt;

&lt;p&gt;Road bike hoods are a perfect example. They’re mass-manufactured to fit some statistical middle ground of hand size and grip preference. My hands are slightly larger than average — not enough that I need custom everything, but enough that stock hoods always feel slightly off after a century ride.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why This Print Matters&lt;/strong&gt;&lt;br&gt;
Being able to model your own hood backing and iterate on the shape changes everything. Too thick? Adjust the model. Want more texture in one area? Add it. Need a different angle for your specific bars? Measure twice, print once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Become a Medium member&lt;/strong&gt;&lt;br&gt;
This is the kind of application that justifies the entire printer for me. Not the ability to make plastic trinkets cheaper than Amazon, but the ability to make things that don't exist for sale anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Material Question&lt;/strong&gt;&lt;br&gt;
Here's where it gets interesting. Hoods need specific properties — grip when your hands are sweaty, some give so they're comfortable, but enough structure that they don't deform when you're pulling hard on a climb. Weather resistance matters too.&lt;/p&gt;

&lt;p&gt;PLA won't survive a wet ride. ABS might work but it's not exactly pleasant against your skin. TPU seems like the obvious choice, though getting the right shore hardness would take some testing. Maybe nylon with a soft-touch coating?&lt;/p&gt;

&lt;p&gt;I haven’t printed cycling components myself yet. My P1S mostly runs functional prints — brackets, organizers, the occasional prototype for a friend’s product display. But seeing functional bike parts like this makes me want to branch out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's Next&lt;/strong&gt;&lt;br&gt;
I need to find a local bike shop that's 3D-printing-curious. Or just suck it up and start modeling my own solutions to the minor annoyances that aren't worth designing and injection-molding, but are absolutely worth a weekend of CAD and a $2 print.&lt;/p&gt;

&lt;p&gt;If you’ve done cycling components, hit me up. I’d love to know what materials actually hold up in the real world — not just in theory, but after six months of road grime and sweat.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
