<?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: Athreya aka Maneshwar</title>
    <description>The latest articles on DEV Community by Athreya aka Maneshwar (@lovestaco).</description>
    <link>https://dev.to/lovestaco</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%2F1002302%2F9dbe5057-f6da-4c08-9b5d-37fe9d281476.png</url>
      <title>DEV Community: Athreya aka Maneshwar</title>
      <link>https://dev.to/lovestaco</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lovestaco"/>
    <language>en</language>
    <item>
      <title>One Rust Unicode Bug, Three Coding Models: A Reproducible Comparison</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Sun, 18 Oct 2026 05:10:27 +0000</pubDate>
      <link>https://dev.to/lovestaco/one-rust-unicode-bug-three-coding-models-a-reproducible-comparison-48ae</link>
      <guid>https://dev.to/lovestaco/one-rust-unicode-bug-three-coding-models-a-reproducible-comparison-48ae</guid>
      <description>&lt;p&gt;Coding-model leaderboards reveal little about how a model handles a real bug. A Rust function can work on ASCII and still panic on valid Unicode because it slices UTF-8 at the wrong byte boundary. The fix must respect character boundaries and preserve the &lt;code&gt;&amp;amp;str&lt;/code&gt; return type. This article uses one reproducible bug, one prompt, and the same regression tests to compare three models on the quality of their patches, not the confidence of their explanations.&lt;/p&gt;

&lt;p&gt;A one-line Rust slice can panic on valid UTF-8. That makes it a useful coding-model challenge. The fix must preserve the function signature, handle character boundaries, and pass regression tests. This comparison uses the same bug for three models and judges their patches using compiler output rather than leaderboard scores.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug
&lt;/h2&gt;

&lt;p&gt;The function below is supposed to return the first &lt;code&gt;n&lt;/code&gt; characters of a string. It works on plain ASCII and panics when &lt;code&gt;n&lt;/code&gt; cuts through a multibyte UTF-8 character.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[cfg(test)]&lt;/span&gt;
&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;#[test]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ascii&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rustacean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"rust"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;#[test]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;unicode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"café"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"café"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;#[test]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;beyond_length&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;assert_eq!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"é"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"é"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original implementation counts bytes, not characters. In UTF-8, the final &lt;code&gt;é&lt;/code&gt; in &lt;code&gt;café&lt;/code&gt; occupies two bytes. Slicing at byte index four cuts through that character and panics. Save the code as &lt;code&gt;src/lib.rs&lt;/code&gt; in a new Cargo library project. Run &lt;code&gt;cargo test&lt;/code&gt; to reproduce the failing Unicode case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three models, one task
&lt;/h2&gt;

&lt;p&gt;The three candidates are &lt;a href="https://aimlapi.com/models/deepseek-v4-pro" rel="noopener noreferrer"&gt;DeepSeek V4 Pro&lt;/a&gt;, &lt;a href="https://aimlapi.com/models/claude-fable-5-1" rel="noopener noreferrer"&gt;Claude Fable 5.1&lt;/a&gt;, and &lt;a href="https://aimlapi.com/models/grok-build-0-1" rel="noopener noreferrer"&gt;Grok Build 0.1&lt;/a&gt;. These are the target model pages for the comparison. Model availability, exact API identifiers, and supported parameters must be checked against the live catalog before execution.&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;API identifier&lt;/th&gt;
&lt;th&gt;Temperature&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek V4 Pro&lt;/td&gt;
&lt;td&gt;Confirm from catalog&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Same Rust file and prompt&lt;/td&gt;
&lt;td&gt;Record API version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Fable 5.1&lt;/td&gt;
&lt;td&gt;Confirm from catalog&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Same Rust file and prompt&lt;/td&gt;
&lt;td&gt;Record API version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok Build 0.1&lt;/td&gt;
&lt;td&gt;Confirm from catalog&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Same Rust file and prompt&lt;/td&gt;
&lt;td&gt;Record API version&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use the same request body, system instructions, maximum output allowance, and test environment for every model. Record the exact model ID returned by the API. A zero temperature reduces randomness but does not guarantee identical answers on repeated calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt
&lt;/h2&gt;

&lt;p&gt;Use the following prompt unchanged for every model.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fix the Rust function below. It must return the first n Unicode scalar values without panicking. Keep the return type &amp;amp;str. Explain the bug briefly, return a complete src/lib.rs, and add a regression test for an emoji and a combining character. Do not add external dependencies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Append the entire original &lt;code&gt;src/lib.rs&lt;/code&gt; after that instruction. Do not tell one model about another model's failure. Do not silently repair a response before testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to inspect in each model's patch
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DeepSeek V4 Pro
&lt;/h3&gt;

&lt;p&gt;Capture the raw response, its reported input and output tokens, the elapsed request time, and the first &lt;code&gt;cargo test&lt;/code&gt; output. A correct patch should iterate over &lt;code&gt;char&lt;/code&gt; boundaries rather than slice at an arbitrary byte index. If the returned code does not compile, retain the error and count the next prompt as another attempt. Add a screenshot of the response and terminal output from the actual run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Fable 5.1
&lt;/h3&gt;

&lt;p&gt;Use the identical source and prompt. Record whether the model preserves the borrowed &lt;code&gt;&amp;amp;str&lt;/code&gt; return type and whether it handles a request longer than the character count. Check the combining-character case carefully. Unicode scalar values are not the same as grapheme clusters. A request for one scalar value from an &lt;code&gt;e&lt;/code&gt; followed by a combining accent may return only the &lt;code&gt;e&lt;/code&gt;. That is correct under this task's definition.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grok Build 0.1
&lt;/h3&gt;

&lt;p&gt;Repeat the procedure without editing the prompt. Review whether the model changes the API, introduces a dependency, or returns a &lt;code&gt;String&lt;/code&gt; instead of &lt;code&gt;&amp;amp;str&lt;/code&gt;. These changes matter even when the visible output looks correct. Keep the first attempt and any follow-up attempts as separate records.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a correct fix looks like
&lt;/h2&gt;

&lt;p&gt;One valid approach uses &lt;code&gt;char_indices&lt;/code&gt; to find the byte boundary after the requested number of scalar values.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;
        &lt;span class="nf"&gt;.char_indices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(|(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)|&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reference implementation is a correctness oracle for the exercise, not an attributed output from any model. The test suite should include ASCII, multibyte characters, empty strings, &lt;code&gt;n&lt;/code&gt; equal to zero, &lt;code&gt;n&lt;/code&gt; greater than the character count, emoji, and decomposed accents. The reference code should be kept out of model prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison scorecard
&lt;/h2&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;First attempt passes&lt;/th&gt;
&lt;th&gt;Attempts&lt;/th&gt;
&lt;th&gt;Wall time&lt;/th&gt;
&lt;th&gt;Input tokens&lt;/th&gt;
&lt;th&gt;Output tokens&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek V4 Pro&lt;/td&gt;
&lt;td&gt;Enter from cargo test&lt;/td&gt;
&lt;td&gt;Enter from logs&lt;/td&gt;
&lt;td&gt;Enter from logs&lt;/td&gt;
&lt;td&gt;Enter from API&lt;/td&gt;
&lt;td&gt;Enter from API&lt;/td&gt;
&lt;td&gt;Compiler and behavior notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Fable 5.1&lt;/td&gt;
&lt;td&gt;Enter from cargo test&lt;/td&gt;
&lt;td&gt;Enter from logs&lt;/td&gt;
&lt;td&gt;Enter from logs&lt;/td&gt;
&lt;td&gt;Enter from API&lt;/td&gt;
&lt;td&gt;Enter from API&lt;/td&gt;
&lt;td&gt;Compiler and behavior notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok Build 0.1&lt;/td&gt;
&lt;td&gt;Enter from cargo test&lt;/td&gt;
&lt;td&gt;Enter from logs&lt;/td&gt;
&lt;td&gt;Enter from logs&lt;/td&gt;
&lt;td&gt;Enter from API&lt;/td&gt;
&lt;td&gt;Enter from API&lt;/td&gt;
&lt;td&gt;Compiler and behavior notes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The scorecard becomes useful once each row is populated from the first response, Cargo output, and API logs. Until then, it is a repeatable evaluation worksheet rather than a model ranking.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to pick for each coding task
&lt;/h2&gt;

&lt;p&gt;For short patches, first-pass compilation and correct edge cases matter more than a polished explanation. For larger refactors, examine how often a model changes unrelated code. For code review, test whether it identifies the byte-versus-character distinction without prompting. For terminal automation, compare how predictably each model follows the required output format.&lt;/p&gt;

&lt;p&gt;The best LLM for coding is task dependent. One Rust bug cannot establish which model performs best across a whole repository, language, or toolchain. It can reveal whether a model handles a concrete failure mode under controlled conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to repeat the comparison
&lt;/h2&gt;

&lt;p&gt;Create a Cargo library with &lt;code&gt;cargo new unicode-prefix --lib&lt;/code&gt;. Replace &lt;code&gt;src/lib.rs&lt;/code&gt; with the buggy example and run &lt;code&gt;cargo test&lt;/code&gt;. Send the same prompt and file to each confirmed model identifier. Save the raw JSON response, request timestamp, token usage fields, and model version. Extract the proposed code into a clean working tree for each attempt. Run &lt;code&gt;cargo fmt --check&lt;/code&gt;, &lt;code&gt;cargo test&lt;/code&gt;, and &lt;code&gt;cargo clippy&lt;/code&gt;. Record failures without modifying the first answer.&lt;/p&gt;

&lt;p&gt;Keep the repository organized as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;baseline/src/lib.rs
oracle/src/lib.rs
prompts/task.txt
runs/&amp;lt;model&amp;gt;/response.json
runs/&amp;lt;model&amp;gt;/cargo-test.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Publish a repository link only after the files are uploaded. The reproducible artifact is the code and logs, not a ranking graphic.&lt;/p&gt;

&lt;p&gt;A shareable comparison includes the repository, unedited first responses, and Cargo logs. Readers can inspect both the proposed fixes and the failures.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>llm</category>
      <category>programming</category>
    </item>
    <item>
      <title>We Rendered a HQ Demo Video on a Free Farm Run by Strangers</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Wed, 14 Oct 2026 11:08:09 +0000</pubDate>
      <link>https://dev.to/lovestaco/we-rendered-a-hq-demo-video-on-a-free-farm-run-by-strangers-1440</link>
      <guid>https://dev.to/lovestaco/we-rendered-a-hq-demo-video-on-a-free-farm-run-by-strangers-1440</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. &lt;a href="https://github.com/HexmosTech/LiveReview/" rel="noopener noreferrer"&gt;Star us&lt;/a&gt; to help devs discover the project, give it a try, and share your feedback to help improve the product.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Last post, my four-laptop render farm lost a race against my own laptop.&lt;/p&gt;

&lt;p&gt;I promised you the ending.&lt;/p&gt;

&lt;p&gt;Here it is.&lt;/p&gt;

&lt;p&gt;It involves a free farm run by strangers and a Java file called &lt;code&gt;sheepit-autoupdate.jar&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson I was too slow to learn
&lt;/h2&gt;

&lt;p&gt;If you missed it, &lt;a href="https://dev.to/lovestaco/we-built-a-blender-render-farm-out-of-four-laptops-1h32"&gt;last time&lt;/a&gt; we turned four laptops into a Flamenco render farm.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/lovestaco/we-built-a-blender-render-farm-out-of-four-laptops-1h32" class="crayons-story__hidden-navigation-link"&gt;We Built a Blender Render Farm Out of Four Laptops&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/lovestaco" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002302%2F9dbe5057-f6da-4c08-9b5d-37fe9d281476.png" alt="lovestaco profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/lovestaco" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Athreya aka Maneshwar
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Athreya aka Maneshwar
                
                
              
              &lt;div id="story-author-preview-content-4839352" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/lovestaco" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002302%2F9dbe5057-f6da-4c08-9b5d-37fe9d281476.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Athreya aka Maneshwar&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/lovestaco/we-built-a-blender-render-farm-out-of-four-laptops-1h32" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 12&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/lovestaco/we-built-a-blender-render-farm-out-of-four-laptops-1h32" id="article-link-4839352"&gt;
          We Built a Blender Render Farm Out of Four Laptops
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/blender"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;blender&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/networking"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;networking&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tailscale"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tailscale&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/lovestaco/we-built-a-blender-render-farm-out-of-four-laptops-1h32" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;25&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/lovestaco/we-built-a-blender-render-farm-out-of-four-laptops-1h32#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            16 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;It worked.&lt;/p&gt;

&lt;p&gt;And then the farm spent almost all of its time downloading a 115 MB .blend over my 5 Mbit/s home upload, while my laptop finished the job alone.&lt;/p&gt;

&lt;p&gt;The takeaway was one sentence:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A render farm is a file distribution problem wearing a GPU costume.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So the question for round two was not "where do I find more GPUs".&lt;/p&gt;

&lt;p&gt;It was "who can I hand a file to once, and have it spread out without my upload being the highway".&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the flock
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.sheepit-renderfarm.com/" rel="noopener noreferrer"&gt;SheepIt&lt;/a&gt; is a free, community-powered render farm for Blender.&lt;/p&gt;

&lt;p&gt;The deal is beautifully simple.&lt;/p&gt;

&lt;p&gt;You install a small client on your machine.&lt;/p&gt;

&lt;p&gt;While your machine is idle, it renders other people's frames.&lt;/p&gt;

&lt;p&gt;That earns you points.&lt;/p&gt;

&lt;p&gt;You spend the points to have the community render yours.&lt;/p&gt;

&lt;p&gt;Everyone lends a machine, everyone gets frames.&lt;/p&gt;

&lt;p&gt;I did not build a farm.&lt;/p&gt;

&lt;p&gt;I joined a flock.&lt;/p&gt;

&lt;p&gt;The client is a single Java jar (&lt;code&gt;sheepit-autoupdate.jar&lt;/code&gt;, which updates itself, hence the name), and the upload side is a web page where you drop a zip.&lt;/p&gt;

&lt;p&gt;That is the entire infrastructure I had to run.&lt;/p&gt;

&lt;p&gt;No Samba, no tailnet, no NAT archaeology.&lt;/p&gt;

&lt;p&gt;The file goes to one place, and it is their problem to hand it to a hundred machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Points: the currency of the flock
&lt;/h2&gt;

&lt;p&gt;SheepIt runs on points.&lt;/p&gt;

&lt;p&gt;You earn them by rendering other people's frames.&lt;/p&gt;

&lt;p&gt;You spend them when you order your own.&lt;/p&gt;

&lt;p&gt;The core loop needs no money, just render time in and render time out.&lt;/p&gt;

&lt;p&gt;I registered on October 10th, the same Saturday the laptop farm gave up, and left the client running.&lt;/p&gt;

&lt;p&gt;Here is our profile a few days later:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk0e48zj602x9dh7uw4y7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk0e48zj602x9dh7uw4y7.png" alt="SheepIt profile page showing 17 projects created, 18,651 frames ordered, 6,981 frames rendered, 2 days 7 hours rendered, rank 416th, 1,261,589 points and 4 consecutive render days"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me read that back to you.&lt;/p&gt;

&lt;p&gt;Our laptops spent &lt;strong&gt;2 days and 7 hours&lt;/strong&gt; rendering strangers' frames.&lt;/p&gt;

&lt;p&gt;That is &lt;strong&gt;6,981 frames&lt;/strong&gt; for other people.&lt;/p&gt;

&lt;p&gt;And in return I ordered &lt;strong&gt;18,651 frames&lt;/strong&gt; across 17 projects.&lt;/p&gt;

&lt;p&gt;I ordered almost three times as many frames as I rendered, and the balance still says &lt;strong&gt;1,261,589 points&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I cannot give you SheepIt's exact exchange rate, so treat this as an observation, not a formula.&lt;/p&gt;

&lt;p&gt;As far as I can tell, a light 360p frame is cheap to order, a heavy 1080p ray traced frame costs more, and a stronger GPU earns faster.&lt;/p&gt;

&lt;p&gt;What I can show you is the shape of it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk0x9vhnwuubchxpy3ffy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk0x9vhnwuubchxpy3ffy.png" alt="Two line charts from the SheepIt profile: points rising from about 270,000 on October 11th to about 1.2 million on October 14th, and frames rendered rising from about 1,400 to about 6,400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The points line only goes up.&lt;/p&gt;

&lt;p&gt;Four days of render streaks, and I never ran dry.&lt;/p&gt;

&lt;p&gt;The frames line flattens after the first couple of days, which is my guess for the laptop getting busier with my own work.&lt;/p&gt;

&lt;p&gt;That is the part I did not expect.&lt;/p&gt;

&lt;p&gt;I thought I was borrowing a farm.&lt;/p&gt;

&lt;p&gt;I was joining a barter economy where the same laptop that edits Miles during the day renders somebody else's sheep at night.&lt;/p&gt;

&lt;p&gt;Everyone is the customer and the supplier at the same time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F16sksp6hfjttlp4uibxf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F16sksp6hfjttlp4uibxf.png" alt="Oprah meme: you lend a GPU, you get a frame, everybody gets a frame"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch: strangers cannot run your scripts
&lt;/h2&gt;

&lt;p&gt;There is exactly one thing SheepIt asks of you, and it is the whole engineering problem.&lt;/p&gt;

&lt;p&gt;Your .blend has to run on a machine you have never seen, unattended, with none of your project around it.&lt;/p&gt;

&lt;p&gt;My scenes were not built for that.&lt;/p&gt;

&lt;p&gt;They are built by Python scripts that load a city, retarget animations, key thousands of values and bake the result into a &lt;code&gt;.blend&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On my laptop that file leans on a whole project folder:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Absolute paths&lt;/strong&gt; to textures, like &lt;code&gt;/home/lovestaco/pers/lr_demo/...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image sequences&lt;/strong&gt; for the animated screens in the city (the bugs board, the queue counters).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sound strips&lt;/strong&gt; for the voice-over.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Render settings&lt;/strong&gt; that my own scripts apply at render time.&lt;/p&gt;

&lt;p&gt;A stranger's machine has none of that.&lt;/p&gt;

&lt;p&gt;It cannot run my scripts, so settings I apply from code simply never happen.&lt;/p&gt;

&lt;p&gt;So I wrote a packer, &lt;code&gt;sheepit_pack.py&lt;/code&gt;, whose job is to turn a built scene into a hermetic one:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pack every image into the .blend.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Textures travel inside the file now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copy image sequences next to it with relative paths.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Blender cannot pack a sequence, so the folder rides along in the zip and the paths become &lt;code&gt;//assets/...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drop the sound strips.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The voice-over and sound effects are mixed later with ffmpeg anyway, so the farm never needs them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bake the settings into the file.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1920x1080 at 100%, 64 samples, ray tracing at full resolution, motion blur on, PNG output.&lt;/p&gt;

&lt;p&gt;Then zip it with the &lt;code&gt;.blend&lt;/code&gt; at the root.&lt;/p&gt;

&lt;p&gt;My zips came out between 130 and 240 MB.&lt;/p&gt;

&lt;p&gt;SheepIt takes up to 2 GB, so that is a comfortable fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trust nothing, verify everything
&lt;/h2&gt;

&lt;p&gt;Here is the thing about sending your scene to strangers.&lt;/p&gt;

&lt;p&gt;If it is broken, you find out hours later, when a stranger's machine has already burned your points on a hundred identical black frames.&lt;/p&gt;

&lt;p&gt;So every package goes through a gate before it leaves my laptop.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Built .blend] --&amp;gt; B[Pack&amp;lt;br/&amp;gt;images, relative paths, baked settings]
    B --&amp;gt; C[Zip&amp;lt;br/&amp;gt;blend at the root]
    C --&amp;gt; D[Extract into a clean folder]
    D --&amp;gt; E{Verify&amp;lt;br/&amp;gt;paths, settings, no sound}
    E --&amp;gt;|fail| X[Fix and repack]
    E --&amp;gt;|pass| F[Render 1 or 2 test frames&amp;lt;br/&amp;gt;from the extracted copy]
    F --&amp;gt; G[Upload the zip]
    G --&amp;gt; H[Community renders]
    H --&amp;gt; I[Download the frames zip]
    I --&amp;gt; J{Frame check&amp;lt;br/&amp;gt;count, gaps, size, black frames}
    J --&amp;gt;|fail| Y[Re-queue those frames]
    J --&amp;gt;|pass| K[Assemble with audio and subtitles]

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef mine fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef farm fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a
    classDef bad fill:#ff9a5c,stroke:#c4642a,color:#1a1a1a
    class E,J decision
    class A,B,C,D,F,K mine
    class G,H,I farm
    class X,Y bad&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The important part is the extraction.&lt;/p&gt;

&lt;p&gt;I do not test the original .blend.&lt;/p&gt;

&lt;p&gt;I unzip the package into a fresh temporary folder, the way the farm will see it, and run the checks there.&lt;/p&gt;

&lt;p&gt;Every external file must exist and be a relative path.&lt;/p&gt;

&lt;p&gt;No sound strips may be left.&lt;/p&gt;

&lt;p&gt;The resolution, samples, ray tracing, motion blur and PNG format must all match what I think I baked.&lt;/p&gt;

&lt;p&gt;Then I render one or two real frames from that extracted copy, with the baked settings.&lt;/p&gt;

&lt;p&gt;If those frames look right, the package is right.&lt;/p&gt;

&lt;p&gt;On my GTX 1650 those test frames take between 7 and 35 seconds at 1080p.&lt;/p&gt;

&lt;p&gt;Which is a quick way to tell the difference between "this will take a day" and "this will take a week" before anyone else spends a point on it.&lt;/p&gt;

&lt;p&gt;I run it as one command per part, and it stops with a loud error at the first problem.&lt;/p&gt;

&lt;p&gt;It also never deletes anything.&lt;/p&gt;

&lt;p&gt;An old package is moved aside, not overwritten.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug where the file was fine and the check was wrong
&lt;/h2&gt;

&lt;p&gt;The first time I ran the new packager for part 1, it died at the zip step with &lt;code&gt;ERROR: street_part_1_final.blend is not at the zip root&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It was at the zip root.&lt;/p&gt;

&lt;p&gt;I looked at the zip.&lt;/p&gt;

&lt;p&gt;The file was right there, first entry.&lt;/p&gt;

&lt;p&gt;The check looked like this, with the fix underneath:&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="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="c"&gt;# broken: dies on SIGPIPE even though the file is in the zip&lt;/span&gt;
unzip &lt;span class="nt"&gt;-Z1&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ZIP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qx&lt;/span&gt; &lt;span class="s2"&gt;"street_part_1_final.blend"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; die &lt;span class="s2"&gt;"not at the zip root"&lt;/span&gt;

&lt;span class="c"&gt;# fixed: capture first, then grep&lt;/span&gt;
&lt;span class="nv"&gt;LIST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;unzip &lt;span class="nt"&gt;-Z1&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ZIP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qx&lt;/span&gt; &lt;span class="s2"&gt;"street_part_1_final.blend"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LIST&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; die &lt;span class="s2"&gt;"not at the zip root"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;grep -q&lt;/code&gt; is polite about one thing: the moment it finds a match, it exits.&lt;/p&gt;

&lt;p&gt;That leaves &lt;code&gt;unzip&lt;/code&gt; writing the rest of the file list into a pipe nobody is reading, so it gets &lt;code&gt;SIGPIPE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;pipefail&lt;/code&gt; says that if any stage of the pipe fails, the whole pipe failed.&lt;/p&gt;

&lt;p&gt;So the file was found, and the check reported it missing.&lt;/p&gt;

&lt;p&gt;The fix is to stop piping.&lt;/p&gt;

&lt;p&gt;If you ever write &lt;code&gt;producer | grep -q&lt;/code&gt; under &lt;code&gt;set -o pipefail&lt;/code&gt;, remember this.&lt;/p&gt;

&lt;p&gt;It works until the producer has more output than the pipe buffer, and then it fails on the one day it matters.&lt;/p&gt;
&lt;h2&gt;
  
  
  Render only what the film keeps
&lt;/h2&gt;

&lt;p&gt;This part is satisfying.&lt;/p&gt;

&lt;p&gt;The film is not one long take.&lt;/p&gt;

&lt;p&gt;Parts of the Blender scenes are cut from the final video (we removed a mural scene, the LED ticker and a subway lightbox), and the 2D product boards in part 3 are not rendered in Blender at all.&lt;/p&gt;

&lt;p&gt;If you upload a scene and ask for "frame 1 to the end", the community renders all of that for you, for free, and you throw half of it away.&lt;/p&gt;

&lt;p&gt;Rude to the sheep.&lt;/p&gt;

&lt;p&gt;So each package gets uploaded with exactly the frame ranges the film needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 0&lt;/strong&gt; is one range, 458 frames.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 1&lt;/strong&gt; is three ranges, because three scenes are cut.&lt;/p&gt;

&lt;p&gt;That skips about 535 frames that never reach the final video.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 2&lt;/strong&gt; is one range, 2,441 frames.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 3&lt;/strong&gt; is the same zip uploaded twice, frames 1 to 664 and 2150 to 3661.&lt;/p&gt;

&lt;p&gt;The 2D boards sitting between them come from a Python script on my laptop in a couple of minutes.&lt;/p&gt;

&lt;p&gt;The whole film is &lt;strong&gt;8,229 frames&lt;/strong&gt;, or 4 minutes 34 seconds.&lt;/p&gt;

&lt;p&gt;Last post I said 7,300.&lt;/p&gt;

&lt;p&gt;The film grew.&lt;/p&gt;

&lt;p&gt;Films do that.&lt;/p&gt;
&lt;h2&gt;
  
  
  Assembling what comes back
&lt;/h2&gt;

&lt;p&gt;Each part comes home as a zip of PNG frames.&lt;/p&gt;

&lt;p&gt;A 1080p part is between 2 and 4 GB.&lt;/p&gt;

&lt;p&gt;I do not trust anything that arrives from a farm of strangers.&lt;/p&gt;

&lt;p&gt;So before a single frame goes into a video, a check script looks at the zip:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the zip intact?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the frame count exactly what the build says it should be, numbered from the first frame to the last, with no gaps and no extras?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does every PNG decode, at exactly the expected size?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is any frame entirely black?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An all-black frame is the farm's way of telling you that a worker had a bad day.&lt;/p&gt;

&lt;p&gt;If everything passes, the frames are hard-linked into a numbered folder (no copies, so a 4 GB set costs nothing extra), ffmpeg encodes the base video, and my assembly script mixes in the voice-over, sound effects, music, subtitles and the frame-number burn-in.&lt;/p&gt;

&lt;p&gt;That last one is for the team.&lt;/p&gt;

&lt;p&gt;When someone says "the thing at f 1201 is off", I can find it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Stale frames beat missing frames
&lt;/h2&gt;

&lt;p&gt;My render scripts skip frames that already exist, which is wonderful when a long render gets interrupted.&lt;/p&gt;

&lt;p&gt;It is terrible after you change the scene.&lt;/p&gt;

&lt;p&gt;Stale frames from the old version sit in the folder, the renderer says "already done", and your new film has the old shot spliced in.&lt;/p&gt;

&lt;p&gt;The rule I follow now: when the scene changes, the old frame folder gets moved aside (not deleted, never wiped), and the render starts into a clean one.&lt;/p&gt;
&lt;h2&gt;
  
  
  A draft at home, the real thing at the farm
&lt;/h2&gt;

&lt;p&gt;Here is the workflow that has been settled for the project, and it is the actual answer to the render bill from last post.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flyjj783bs0zbnb2oydw0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flyjj783bs0zbnb2oydw0.png" alt="Diagram of two loops: a cheap 360p review loop on my laptop that ends with a 10 MB copy for the team, and the 1080p SheepIt farm path of pack, verify, upload, community render, download and assemble"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drafts are local.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;360p, EEVEE, no ray tracing, no motion blur.&lt;/p&gt;

&lt;p&gt;About 0.7 seconds per frame, so part 2's 2,441 frames take roughly 30 minutes.&lt;/p&gt;

&lt;p&gt;The whole film is about two hours.&lt;/p&gt;

&lt;p&gt;Make chai, come back, watch Miles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finals are on the farm.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1080p, 64 samples, ray traced reflections, motion blur.&lt;/p&gt;

&lt;p&gt;A day of laptop time, spread across a flock that does not need me.&lt;/p&gt;

&lt;p&gt;And between them sits the thing that makes drafts useful: a review copy the team can actually open.&lt;/p&gt;
&lt;h2&gt;
  
  
  Did Miles get his 1080p?
&lt;/h2&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;Two of the four parts have already come home at 1080p from the flock, a 2 GB zip of PNGs for the opening and 3.7 GB for part 2.&lt;/p&gt;

&lt;p&gt;Then the team's notes arrived and the film changed under them.&lt;/p&gt;

&lt;p&gt;So there is a fresh set of packages sitting verified and ready for the community, each one with a test frame already rendered from the extracted copy.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I would tell last week's me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do not build a farm when a community already runs one.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Four laptops taught me a lot about networking.&lt;/p&gt;

&lt;p&gt;A flock taught me that I had been solving the wrong problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make your scene hermetic before you make it fast.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the file only works on your machine, it works on one machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify the package, not the original.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Extract it into a clean folder and render a frame from that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep a cheap draft loop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is the thing that lets a teammate say "this word is wrong" without it costing you a day.&lt;/p&gt;

&lt;p&gt;And a thank you to the strangers whose GPUs are going to render the final.&lt;/p&gt;

&lt;p&gt;You are not a sheep.&lt;/p&gt;

&lt;p&gt;You are the shepherd.&lt;/p&gt;

&lt;p&gt;I would love to hear from you in the comments.&lt;/p&gt;

&lt;p&gt;Have you ever lent your machine to a community farm, or used a community compute network, for a project?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Follow along, because next time I will show you the final cut.&lt;/strong&gt;&lt;/p&gt;



&lt;p&gt;&lt;em&gt;&lt;br&gt;
Your team's attention is limited, and the deluge of AI-generated code is making it harder to keep production secure and reliable without slowing you down.&lt;/em&gt;&lt;/p&gt;
&lt;em&gt;

&lt;p&gt;I'm building &lt;strong&gt;LiveReview&lt;/strong&gt;, a blast-radius aware AI code review built for your business-critical systems.&lt;/p&gt;

&lt;p&gt;Instead of presenting every diff with equal emphasis, &lt;strong&gt;LiveReview scores each change by blast radius — how far its impact reaches through your call graph — so you can focus attention where it actually matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spend code review effort where business risk is highest — not spread evenly across every diff.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;
        LiveReview
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Risk-Aware AI Code Review for Business-Critical Systems
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/png/logo-with-text.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fpng%2Flogo-with-text.png" alt="LiveReview" height="80"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml" rel="noopener noreferrer"&gt;&lt;img alt="mcp-testcases.yml" title="mcp-testcases.yml: MCP integration test suite" src="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml/badge.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LiveReview: Risk-Aware AI Code Review for Business-Critical Systems&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;LiveReview is an AI code reviewer that scores every hunk of a diff by &lt;strong&gt;blast radius&lt;/strong&gt;: how far a change reaches through your call graph, how much persistent state it touches, and how well-tested it is. A 3-line change to a shared auth check can outrank a 300-line UI tweak. Your team's attention goes to the highest-risk code first, not spread evenly across every diff.&lt;/p&gt;


  
    
    &lt;span class="m-1"&gt;blast-radius-demo.mp4&lt;/span&gt;
  

  

  


&lt;p&gt;&lt;i&gt;LiveReview's Blast Radius &amp;amp; Review Priority scoring, live in the diff viewer.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;The exact math, not a black box&lt;/th&gt;

&lt;th&gt;Visualize blast radius at a glance&lt;/th&gt;

&lt;th&gt;Every factor that feeds the score&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-3.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-3.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-4.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-4.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-2.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-2.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;&lt;/p&gt;

How does Blast Radius scoring work? (a more technical explanation)

&lt;p&gt;&lt;strong&gt;Here's the goal:&lt;/strong&gt;&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;A 3-line fix in a function used by 40 other files, that also writes to a database, should score high.&lt;/li&gt;

&lt;li&gt;A 300-line UI change in one file, fully covered by tests…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;b&gt;Click below to try LiveReview with your codebase:&lt;/b&gt;&lt;/p&gt;

&lt;/em&gt;&lt;p&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hexmos.com/livereview" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvls0pq7nymbrll98je6s.png" alt="LiveReview Banner"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blender</category>
      <category>python</category>
      <category>devops</category>
      <category>productivity</category>
    </item>
    <item>
      <title>We Built a Blender Render Farm Out of Four Laptops</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Mon, 12 Oct 2026 17:06:37 +0000</pubDate>
      <link>https://dev.to/lovestaco/we-built-a-blender-render-farm-out-of-four-laptops-1h32</link>
      <guid>https://dev.to/lovestaco/we-built-a-blender-render-farm-out-of-four-laptops-1h32</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. &lt;a href="https://github.com/HexmosTech/LiveReview/" rel="noopener noreferrer"&gt;Star us&lt;/a&gt; to help devs discover the project, give it a try, and share your feedback to help improve the product.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We built a render farm out of four laptops on a Saturday evening.&lt;/p&gt;

&lt;p&gt;It had a job queue, a web dashboard, a private network spanning three cities' worth of ISPs, and a shared drive that every machine could see.&lt;/p&gt;

&lt;p&gt;It worked.&lt;/p&gt;

&lt;p&gt;And then it lost a race against my own laptop, rendering alone.&lt;/p&gt;

&lt;p&gt;This is the story of that farm, the networking rabbit hole it dropped us into, and why a 130 MB file taking seven minutes to cross the internet ended the whole experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where we left Miles
&lt;/h2&gt;

&lt;p&gt;If you have been following along, you know the setup.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7"&gt;part one&lt;/a&gt; I rigged a Spider-Man model in Blender without ever having opened Blender before.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7" class="crayons-story__hidden-navigation-link"&gt;I Made Spider-Man Swing Without Animating a Single Frame&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/lovestaco" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002302%2F9dbe5057-f6da-4c08-9b5d-37fe9d281476.png" alt="lovestaco profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/lovestaco" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Athreya aka Maneshwar
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Athreya aka Maneshwar
                
                
              
              &lt;div id="story-author-preview-content-4793045" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/lovestaco" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002302%2F9dbe5057-f6da-4c08-9b5d-37fe9d281476.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Athreya aka Maneshwar&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 3&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7" id="article-link-4793045"&gt;
          I Made Spider-Man Swing Without Animating a Single Frame
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/blender"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;blender&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;48&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              7&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            13 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;In &lt;a href="https://dev.to/lovestaco/spider-man-moonwalked-off-a-billboard-and-only-my-test-suite-noticed-46k7"&gt;part two&lt;/a&gt; Miles moonwalked off a billboard and a Python test caught it.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/lovestaco/spider-man-moonwalked-off-a-billboard-and-only-my-test-suite-noticed-46k7" class="crayons-story__hidden-navigation-link"&gt;Spider-Man Moonwalked Off a Billboard and Only My Test Suite Noticed&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/lovestaco" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002302%2F9dbe5057-f6da-4c08-9b5d-37fe9d281476.png" alt="lovestaco profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/lovestaco" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Athreya aka Maneshwar
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Athreya aka Maneshwar
                
                
              
              &lt;div id="story-author-preview-content-4803195" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/lovestaco" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002302%2F9dbe5057-f6da-4c08-9b5d-37fe9d281476.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Athreya aka Maneshwar&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/lovestaco/spider-man-moonwalked-off-a-billboard-and-only-my-test-suite-noticed-46k7" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 5&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/lovestaco/spider-man-moonwalked-off-a-billboard-and-only-my-test-suite-noticed-46k7" id="article-link-4803195"&gt;
          Spider-Man Moonwalked Off a Billboard and Only My Test Suite Noticed
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/blender"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;blender&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/python"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;python&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gamedev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gamedev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/debugging"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;debugging&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/lovestaco/spider-man-moonwalked-off-a-billboard-and-only-my-test-suite-noticed-46k7" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;25&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/lovestaco/spider-man-moonwalked-off-a-billboard-and-only-my-test-suite-noticed-46k7#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              1&lt;span class="hidden s:inline"&gt;&amp;nbsp;comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            14 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;Since then, the film has grown into a roughly four minute LiveReview explainer in three parts.&lt;/p&gt;

&lt;p&gt;That is about &lt;strong&gt;7,300 frames&lt;/strong&gt; at 30 fps.&lt;/p&gt;

&lt;p&gt;And every one of them has to be rendered by something.&lt;/p&gt;

&lt;h2&gt;
  
  
  360p is a lie we tell ourselves
&lt;/h2&gt;

&lt;p&gt;For review drafts, I render at 360p with EEVEE, Blender's real-time engine, at 8 samples with ray tracing and motion blur switched off.&lt;/p&gt;

&lt;p&gt;On my laptop's GTX 1650 that comes out to about one second per frame.&lt;/p&gt;

&lt;p&gt;A single part renders in 20 to 50 minutes.&lt;/p&gt;

&lt;p&gt;Go make chai, come back, watch Miles swing.&lt;/p&gt;

&lt;p&gt;Totally fine.&lt;/p&gt;

&lt;p&gt;Then you want the real thing.&lt;/p&gt;

&lt;p&gt;1080p, ray traced reflections on the wet street, proper sample counts.&lt;/p&gt;

&lt;p&gt;So I asked Claude to stop guessing and time actual frames from our scene.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fni80xsnuqvz05fkmatff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fni80xsnuqvz05fkmatff.png" alt="Bar chart of whole film render time at four quality levels, from about 2 hours at 360p to about 7 days with Cycles, plus a panel showing 2 workers are 1.3x faster and 3 workers are slower"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The 1080p EEVEE versions we actually wanted sit between &lt;strong&gt;10 and 22 hours&lt;/strong&gt; for the whole film.&lt;/p&gt;

&lt;p&gt;Cycles, the path tracer, came in at 76 to 90 seconds per frame.&lt;/p&gt;

&lt;p&gt;That is a week.&lt;/p&gt;

&lt;p&gt;Of my laptop doing nothing else.&lt;/p&gt;

&lt;p&gt;With 6 GB of disk left.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Is it not even using the GPU?"
&lt;/h3&gt;

&lt;p&gt;That is the first question anyone asks, and it is a fair one.&lt;/p&gt;

&lt;p&gt;So we sampled &lt;code&gt;nvidia-smi&lt;/code&gt; during a render.&lt;/p&gt;

&lt;p&gt;Blender was on the card, using about 1.5 GB of the 4 GB VRAM, at roughly 44% utilization.&lt;/p&gt;

&lt;p&gt;At 360p the GPU finishes its part of the frame quickly and then sits there waiting for the CPU, which is busy posing the characters, evaluating the crowd and writing the JPEG.&lt;/p&gt;

&lt;p&gt;So the next obvious move was to run more Blender processes in parallel.&lt;/p&gt;

&lt;p&gt;Two workers were only 1.3x faster than one.&lt;/p&gt;

&lt;p&gt;Three workers were &lt;em&gt;slower than one&lt;/em&gt;, because three Blender processes fighting over eight cores and one GPU is not parallelism, it is a custody battle.&lt;/p&gt;

&lt;p&gt;The only free speed-up we found locally was rendering each frame range as a single animation call instead of calling &lt;code&gt;render()&lt;/code&gt; once per frame, which skips redoing scene setup every time.&lt;/p&gt;

&lt;p&gt;That bought about 22% on one worker.&lt;/p&gt;

&lt;p&gt;Nice, but nobody turns a 22 hour render into an overnight one with a 22% discount.&lt;/p&gt;

&lt;p&gt;The conclusion was pretty clear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We did not need a faster script. We needed more GPUs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And there were three other laptops in the team, mostly idle on a Saturday evening.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Flamenco
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://flamenco.blender.org/" rel="noopener noreferrer"&gt;Flamenco&lt;/a&gt; is Blender's own open source render farm software.&lt;/p&gt;

&lt;p&gt;It is a lot smaller than the phrase "render farm software" suggests.&lt;/p&gt;

&lt;p&gt;There are exactly two programs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flamenco Manager&lt;/strong&gt; is the brain.&lt;/p&gt;

&lt;p&gt;It holds the job queue, serves a web dashboard, and splits each job into tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flamenco Worker&lt;/strong&gt; is the muscle.&lt;/p&gt;

&lt;p&gt;It runs on each rendering machine, polls the manager for work, launches Blender in the background, and reports progress.&lt;/p&gt;

&lt;p&gt;Both are single Go binaries you download, extract and run.&lt;/p&gt;

&lt;p&gt;No database server, no Kubernetes, no YAML archaeology.&lt;/p&gt;

&lt;p&gt;When you submit a job, say "render frames 2370 to 2429 of this .blend", the manager cuts the range into &lt;strong&gt;chunks&lt;/strong&gt; of N frames.&lt;/p&gt;

&lt;p&gt;Each chunk becomes a task.&lt;/p&gt;

&lt;p&gt;Every idle worker grabs the next task, renders it, and asks for another, so a fast machine naturally ends up doing more of the work than a slow one.&lt;/p&gt;

&lt;p&gt;While a task runs, the worker keeps pinging the manager with a "may I keep running?" check, which is how cancelling a job actually reaches a laptop in another city.&lt;/p&gt;

&lt;p&gt;If a task fails, it gets retried on a different worker.&lt;/p&gt;

&lt;p&gt;After three failures on the same job, that worker gets blocklisted for the job so it stops eating chunks.&lt;/p&gt;

&lt;p&gt;Here is the farm we built:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    ME[My laptop&amp;lt;br/&amp;gt;Manager + Worker]
    SMB[(spidey share&amp;lt;br/&amp;gt;Samba)]
    TS{{Tailscale&amp;lt;br/&amp;gt;private network}}
    SH[Windows desktop&amp;lt;br/&amp;gt;later WSL]
    LI[pop-os laptop]
    RU[ASUS TUF laptop]

    ME --- SMB
    ME &amp;lt;--&amp;gt;|jobs + status| TS
    TS &amp;lt;--&amp;gt; SH
    TS &amp;lt;--&amp;gt; LI
    TS &amp;lt;--&amp;gt; RU
    SH -.-&amp;gt;|reads .blend, writes frames| SMB
    LI -.-&amp;gt;|reads .blend, writes frames| SMB
    RU -.-&amp;gt;|reads .blend, writes frames| SMB

    classDef host fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef net fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef store fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a
    classDef peer fill:#ff9a5c,stroke:#c4642a,color:#1a1a1a
    class ME host
    class TS net
    class SMB store
    class SH,LI,RU peer&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Notice the dotted lines.&lt;/p&gt;

&lt;p&gt;That is the part that would end up mattering.&lt;/p&gt;

&lt;h3&gt;
  
  
  The one assumption Flamenco makes
&lt;/h3&gt;

&lt;p&gt;Flamenco does not move your files around.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://flamenco.blender.org/usage/shared-storage/" rel="noopener noreferrer"&gt;shared storage docs&lt;/a&gt; are upfront about it: the manager and every worker must be able to see the same files, on a NAS or a file server or some other shared drive.&lt;/p&gt;

&lt;p&gt;The job just says "open this path", and it is your problem to make sure that path exists everywhere.&lt;/p&gt;

&lt;p&gt;On four laptops scattered across home connections, that sounds like a strange requirement.&lt;/p&gt;

&lt;p&gt;To be fair to Flamenco, a studio render farm lives on one fast LAN with one NAS, and this is a completely reasonable design there.&lt;/p&gt;

&lt;p&gt;Our "LAN" was four laptops on four home connections.&lt;/p&gt;

&lt;p&gt;Which brings us to the networking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tailscale: a LAN made out of the internet
&lt;/h2&gt;

&lt;p&gt;My first worry was the obvious one: does this mean my machine has to push everything over my internet connection?&lt;/p&gt;

&lt;p&gt;It does not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tailscale.com/" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt; just creates a private network so all our laptops can talk to each other.&lt;/p&gt;

&lt;p&gt;Every machine runs the Tailscale client and logs into the same account (a "tailnet").&lt;/p&gt;

&lt;p&gt;Each machine gets a stable private address in the &lt;code&gt;100.x.y.z&lt;/code&gt; range, and every pair of machines talks over an encrypted &lt;a href="https://www.wireguard.com/" rel="noopener noreferrer"&gt;WireGuard&lt;/a&gt; tunnel.&lt;/p&gt;

&lt;p&gt;Tailscale's servers only do the coordination: who is in the network, what their public keys are, where they might be reachable.&lt;/p&gt;

&lt;p&gt;Your actual traffic is supposed to go machine to machine.&lt;/p&gt;

&lt;p&gt;"Supposed to" is doing a lot of work in that sentence, and we will get there.&lt;/p&gt;

&lt;p&gt;On my laptop, Claude set up a Samba share called &lt;code&gt;spidey&lt;/code&gt; and kept it bound to loopback only, so it was never exposed to my Wi-Fi or the internet.&lt;/p&gt;

&lt;p&gt;Then Tailscale itself forwarded the SMB port, so only machines on the tailnet could reach it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# on my laptop: Samba listens on 127.0.0.1 only,&lt;/span&gt;
&lt;span class="c"&gt;# Tailscale forwards tailnet traffic on 445 to it&lt;/span&gt;
tailscale serve &lt;span class="nt"&gt;--bg&lt;/span&gt; &lt;span class="nt"&gt;--tcp&lt;/span&gt; 445 tcp://127.0.0.1:445

&lt;span class="c"&gt;# on each Linux worker: mount it at the same path everywhere&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; cifs //100.76.212.8/spidey /mnt/spidey &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/root/.spidey_smb,uid&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;,gid&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;,vers&lt;span class="o"&gt;=&lt;/span&gt;3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The first milestone was gloriously low-tech.&lt;/p&gt;

&lt;p&gt;Someone created a file from Windows.&lt;/p&gt;

&lt;p&gt;I created one from Linux.&lt;/p&gt;

&lt;p&gt;Both files showed up on both machines.&lt;/p&gt;

&lt;p&gt;We celebrated a text file like it was a product launch.&lt;/p&gt;
&lt;h2&gt;
  
  
  The path boss fight
&lt;/h2&gt;

&lt;p&gt;Having a shared folder is step one.&lt;/p&gt;

&lt;p&gt;Getting Blender on three operating systems to agree on what is &lt;em&gt;in&lt;/em&gt; it is the actual game.&lt;/p&gt;
&lt;h3&gt;
  
  
  Paths baked into the .blend
&lt;/h3&gt;

&lt;p&gt;Claude inspected &lt;code&gt;street_part2_b.blend&lt;/code&gt; and found 31 external asset references.&lt;/p&gt;

&lt;p&gt;25 of them were absolute paths starting with &lt;code&gt;/home/lovestaco/pers/lr_demo/...&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Windows desktop has no &lt;code&gt;/home/lovestaco&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Neither does the pop-os laptop.&lt;/p&gt;

&lt;p&gt;The fix was to copy the project into the share and convert every reference to a Blender relative path (&lt;code&gt;//assets/...&lt;/code&gt;), resolved from wherever the .blend itself lives.&lt;/p&gt;

&lt;p&gt;After that, all three street files had zero absolute paths left.&lt;/p&gt;
&lt;h3&gt;
  
  
  Paths in the job itself
&lt;/h3&gt;

&lt;p&gt;The .blend location and output folder in a Flamenco job are a separate problem, and Flamenco does solve this one with &lt;strong&gt;two-way variables&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The manager stores job paths with a placeholder, and each worker swaps in its own platform's value:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# flamenco-manager.yaml&lt;/span&gt;
&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;spidey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;is_twoway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/mnt/spidey&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;windows&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;D:\spidey&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Two gotchas we learned the hard way.&lt;/p&gt;

&lt;p&gt;On Windows, Flamenco wants a &lt;strong&gt;drive letter&lt;/strong&gt;, not a &lt;code&gt;\\server\share&lt;/code&gt; UNC path.&lt;/p&gt;

&lt;p&gt;And on Linux, every machine has to use the &lt;em&gt;same&lt;/em&gt; mount point, because there is only one Linux value.&lt;/p&gt;

&lt;p&gt;That is why everyone mounted at &lt;code&gt;/mnt/spidey&lt;/code&gt;, and why the wizard rejected &lt;code&gt;~/spidey&lt;/code&gt; outright (it does not expand &lt;code&gt;~&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Creating &lt;code&gt;D:\spidey&lt;/code&gt; with &lt;code&gt;mklink&lt;/code&gt; also failed, which turned out to be because &lt;code&gt;mklink&lt;/code&gt; is a builtin of the old Command Prompt, not of PowerShell.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cmd /c mklink ...&lt;/code&gt; from an admin window fixed it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Where is Blender?
&lt;/h3&gt;

&lt;p&gt;Then the Windows worker printed this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There was an error finding Blender on this system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Blender was on PATH.&lt;/p&gt;

&lt;p&gt;The shell had been restarted.&lt;/p&gt;

&lt;p&gt;It still could not find it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0rmlt9px947pdav0b1pl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0rmlt9px947pdav0b1pl.png" alt="Monkey puppet side-eye meme about Flamenco on Windows looking for the program that opens .blend files instead of checking PATH"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://flamenco.blender.org/usage/variables/blender/" rel="noopener noreferrer"&gt;Blender variable docs&lt;/a&gt; explain why: on Windows the worker looks up which program is associated with &lt;code&gt;.blend&lt;/code&gt; files, rather than trusting PATH.&lt;/p&gt;

&lt;p&gt;The fix was gloriously petty.&lt;/p&gt;

&lt;p&gt;Create an empty junk .blend file, use it to set the file association, and the worker immediately reported &lt;code&gt;Blender 5.2.2 LTS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By 8:15 pm we had four workers awake on the dashboard:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;image pending:&lt;/strong&gt; Flamenco web dashboard showing four workers awake and the farm status idle&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My laptop, a Windows desktop, a pop-os laptop and an ASUS TUF gaming laptop.&lt;/p&gt;

&lt;p&gt;All on Blender 5.2.2.&lt;/p&gt;

&lt;p&gt;An actual render farm.&lt;/p&gt;

&lt;p&gt;On a Saturday.&lt;/p&gt;
&lt;h2&gt;
  
  
  The first job
&lt;/h2&gt;

&lt;p&gt;We kept it small on purpose.&lt;/p&gt;

&lt;p&gt;60 frames of part 2b at 360p draft quality, split into three chunks of 20.&lt;/p&gt;

&lt;p&gt;Three workers picked up a chunk each within seconds.&lt;/p&gt;

&lt;p&gt;Frames started landing in the shared folder.&lt;/p&gt;

&lt;p&gt;And the farm genuinely worked.&lt;/p&gt;

&lt;p&gt;When the job finished, Claude compared frames rendered by three different machines and they were identical: same lighting, same colours, same Miles.&lt;/p&gt;

&lt;p&gt;That part is honestly very cool.&lt;/p&gt;

&lt;p&gt;Then we looked at &lt;em&gt;how long&lt;/em&gt; everything took.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fngjzysqonmmuy0glvru2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fngjzysqonmmuy0glvru2.png" alt="Bar chart of how long each worker spent reading the 115 MB .blend: about 3 seconds locally, over 3 minutes on the relayed laptops, 82 seconds then a failure on Windows, and about 6 minutes on WSL"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My worker read the 115 MB .blend off its own disk in a few seconds and rendered at about 0.4 s per frame.&lt;/p&gt;

&lt;p&gt;The two relayed laptops spent over &lt;strong&gt;three minutes&lt;/strong&gt; just opening the file before rendering a single frame.&lt;/p&gt;

&lt;p&gt;A 20 frame chunk at 360p renders in seconds.&lt;/p&gt;

&lt;p&gt;So the farm was spending almost all its time downloading and almost none of it rendering.&lt;/p&gt;
&lt;h3&gt;
  
  
  "Missing DNA block"
&lt;/h3&gt;

&lt;p&gt;The Windows worker did something more dramatic.&lt;/p&gt;

&lt;p&gt;It spent 82 seconds reading the file and then Blender died with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ERROR Failed to read blend file 'D:\spidey...\street_part2_b_360p.blend': Missing DNA block&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This error is a great little window into how .blend files work.&lt;/p&gt;

&lt;p&gt;A .blend is basically a memory dump.&lt;/p&gt;

&lt;p&gt;It is a header followed by a long run of data blocks, each one a chunk of Blender's in-memory structs written straight to disk.&lt;/p&gt;

&lt;p&gt;To read those structs back, Blender needs to know their layout, which fields, which types, which offsets, and that description lives in a special block called &lt;strong&gt;DNA1&lt;/strong&gt; (the "SDNA").&lt;/p&gt;

&lt;p&gt;That is how a file saved years ago, by a Blender with differently shaped structs, can still be opened today.&lt;/p&gt;

&lt;p&gt;The catch is that DNA1 gets written near the &lt;strong&gt;end&lt;/strong&gt; of the file.&lt;/p&gt;

&lt;p&gt;So if a read dies partway through, Blender never reaches the block that explains everything before it, and you get "Missing DNA block".&lt;/p&gt;

&lt;p&gt;We checked the obvious thing first.&lt;/p&gt;

&lt;p&gt;The copy on Windows had the right size, 114,783,416 bytes, and once its SHA-256 finally finished (that alone took ages over the link), the hash matched mine exactly.&lt;/p&gt;

&lt;p&gt;The file was fine.&lt;/p&gt;

&lt;p&gt;The read was not.&lt;/p&gt;
&lt;h3&gt;
  
  
  The chunk size argument
&lt;/h3&gt;

&lt;p&gt;This sparked the best debate of the night.&lt;/p&gt;

&lt;p&gt;Claude suggested bigger chunks, so each worker would pay the slow load less often.&lt;/p&gt;

&lt;p&gt;The obvious pushback: wouldn't smaller chunks download faster, in more pieces, spread across more machines?&lt;/p&gt;

&lt;p&gt;The intuition makes sense if chunks map to pieces of the file, like a torrent.&lt;/p&gt;

&lt;p&gt;They do not.&lt;/p&gt;

&lt;p&gt;A chunk is a range of &lt;em&gt;frames&lt;/em&gt;, but every chunk opens the &lt;em&gt;entire&lt;/em&gt; scene.&lt;/p&gt;

&lt;p&gt;Smaller chunks mean more full reads, not smaller ones.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu0k1nqkmpwgycq0m95aa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu0k1nqkmpwgycq0m95aa.png" alt="Bernie Sanders meme: every 10 frame chunk, I am once again asking for the whole 115 MB .blend"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next question was a better one though: once the file has been read, isn't it done? Shouldn't only the first read be slow, since the OS caches the file?&lt;/p&gt;

&lt;p&gt;So we ran a second job with 10 frame chunks and watched the ASUS laptop take its second chunk.&lt;/p&gt;

&lt;p&gt;The first read took 3 min 02 s.&lt;/p&gt;

&lt;p&gt;The second took &lt;strong&gt;31 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Caching was real.&lt;/p&gt;

&lt;p&gt;Not instant, but real.&lt;/p&gt;

&lt;p&gt;On Windows, though, the cached retry failed with the same "Missing DNA block", so that SMB client and Blender were clearly not getting along.&lt;/p&gt;

&lt;p&gt;Moving that worker into WSL got past the error, and it promptly rendered at about 50 seconds per frame, which smells a lot like EEVEE running without access to the GPU.&lt;/p&gt;
&lt;h3&gt;
  
  
  A side quest: whose GPU is it anyway
&lt;/h3&gt;

&lt;p&gt;Even after loading, the remote workers were slow per frame.&lt;/p&gt;

&lt;p&gt;The pop-os laptop took about 2.3 s per frame against my 0.4 s, and its GPU sampling step alone was over 3x slower than mine.&lt;/p&gt;

&lt;p&gt;Its &lt;code&gt;lspci&lt;/code&gt; explained it: an NVIDIA GTX 1650 Mobile, &lt;em&gt;and&lt;/em&gt; an AMD Radeon Vega built into the Ryzen processor.&lt;/p&gt;

&lt;p&gt;Two graphics chips, like most gaming laptops.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;glxinfo&lt;/code&gt; said OpenGL was running on the AMD integrated chip by default, because that is what laptops do to save battery.&lt;/p&gt;

&lt;p&gt;EEVEE was happily rendering Miles on the weak chip while the GTX 1650 sat there.&lt;/p&gt;

&lt;p&gt;The fix is PRIME render offload, starting the worker with &lt;code&gt;__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia&lt;/code&gt; so Blender lands on the NVIDIA card.&lt;/p&gt;

&lt;p&gt;Worth knowing if you ever farm out renders to other people's laptops.&lt;/p&gt;

&lt;p&gt;Their GPU is not necessarily the GPU Blender gets.&lt;/p&gt;
&lt;h2&gt;
  
  
  Direct vs DERP (and what on earth is "blr")
&lt;/h2&gt;

&lt;p&gt;Back to the real problem, the reads.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tailscale status&lt;/code&gt; on my machine had one telling word at the end of each peer's line.&lt;/p&gt;

&lt;p&gt;The WSL worker was connected &lt;strong&gt;direct&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The pop-os and ASUS laptops were going through &lt;strong&gt;relay "blr"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is what those two words actually mean.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frjaeajvpkn39j5t2ng03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frjaeajvpkn39j5t2ng03.png" alt="Diagram comparing a direct WireGuard UDP tunnel between the WSL worker and my laptop at about 97 ms ping with the pop-os laptop's traffic relayed through Tailscale's DERP server in Bengaluru at 58 to 415 ms, plus a note that it sits behind a hard NAT"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Direct
&lt;/h3&gt;

&lt;p&gt;Tailscale's first choice is a direct WireGuard tunnel over UDP between the two machines.&lt;/p&gt;

&lt;p&gt;The hard part is that almost nobody's laptop has a public IP.&lt;/p&gt;

&lt;p&gt;You are behind your home router's NAT, and quite often behind your ISP's NAT too.&lt;/p&gt;

&lt;p&gt;So Tailscale does &lt;strong&gt;NAT traversal&lt;/strong&gt;, also known as hole punching.&lt;/p&gt;

&lt;p&gt;Each machine discovers what its public IP and port look like from the outside, the coordination server shares those with the peer, and both sides fire UDP packets at each other at the same time.&lt;/p&gt;

&lt;p&gt;Each router sees an outgoing packet, creates a mapping for the reply, and if the timing and ports line up, the two outgoing "holes" meet in the middle.&lt;/p&gt;

&lt;p&gt;Tailscale's &lt;a href="https://tailscale.com/blog/how-nat-traversal-works" rel="noopener noreferrer"&gt;How NAT traversal works&lt;/a&gt; is one of the best networking posts on the internet if you want the full version.&lt;/p&gt;
&lt;h3&gt;
  
  
  DERP
&lt;/h3&gt;

&lt;p&gt;When hole punching fails, Tailscale falls back to &lt;a href="https://tailscale.com/kb/1232/derp-servers" rel="noopener noreferrer"&gt;DERP&lt;/a&gt;, short for &lt;strong&gt;Designated Encrypted Relay for Packets&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;DERP servers are relays that Tailscale runs around the world.&lt;/p&gt;

&lt;p&gt;Your packets go to the relay over HTTPS and it forwards them to the other machine.&lt;/p&gt;

&lt;p&gt;They stay WireGuard-encrypted end to end, so the relay cannot read them, it just shuffles opaque bytes.&lt;/p&gt;

&lt;p&gt;DERP regions are named after airport-style city codes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"blr" is Bengaluru.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So "relay blr" just means "that laptop's packets to me were taking a detour through Tailscale's Bengaluru relay".&lt;/p&gt;

&lt;p&gt;Direct is faster for the reasons you would guess.&lt;/p&gt;

&lt;p&gt;A relay adds an extra hop, it is a shared server rather than your own path, and it runs over TCP, so a congested relay hurts you in ways a direct UDP path does not.&lt;/p&gt;

&lt;p&gt;Pings from the pop-os laptop through blr ranged from &lt;strong&gt;58 ms to 415 ms&lt;/strong&gt;, and one test run stalled after receiving about 159 MB, with even a plain &lt;code&gt;ls&lt;/code&gt; on the share hanging for two minutes.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why it could not go direct
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;tailscale netcheck&lt;/code&gt; on the pop-os laptop said:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* UDP: true
* IPv6: no, but OS has support
* MappingVariesByDestIP: true
* PortMapping: UPnP
* Nearest DERP: Bengaluru (blr: 48.6ms)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The important line is &lt;code&gt;MappingVariesByDestIP: true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is a "hard" NAT.&lt;/p&gt;

&lt;p&gt;Its router assigns a &lt;em&gt;different&lt;/em&gt; public port for every destination, so the port my laptop learns about is not the port that router will actually accept traffic on from me.&lt;/p&gt;

&lt;p&gt;Hole punching needs at least one side to be predictable.&lt;/p&gt;

&lt;p&gt;Mine was the easy kind, but my router offered no UPnP or NAT-PMP to open a port automatically, and that side had no IPv6 to sidestep the whole mess.&lt;/p&gt;

&lt;p&gt;Here is roughly how Tailscale decides:&lt;br&gt;
&lt;/p&gt;
&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Two peers want to talk] --&amp;gt; B{Either side has&amp;lt;br/&amp;gt;a public, open port?}
    B --&amp;gt;|yes| D[Direct WireGuard over UDP]
    B --&amp;gt;|no| C{Both NATs map ports&amp;lt;br/&amp;gt;predictably?}
    C --&amp;gt;|yes| E{Hole punch succeeds?}
    C --&amp;gt;|no, hard NAT| F{IPv6 on both sides?}
    E --&amp;gt;|yes| D
    E --&amp;gt;|no| G[Relay through nearest DERP]
    F --&amp;gt;|yes| D
    F --&amp;gt;|no| G

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef good fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef bad fill:#ff9a5c,stroke:#c4642a,color:#1a1a1a
    classDef start fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
    class B,C,E,F decision
    class D good
    class G bad
    class A start&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The suggested fix was forwarding UDP 41641 on my router so the hard NAT side would have an open door to knock on.&lt;/p&gt;
&lt;h3&gt;
  
  
  The twist: direct did not save us either
&lt;/h3&gt;

&lt;p&gt;Then we noticed something awkward.&lt;/p&gt;

&lt;p&gt;The WSL worker &lt;em&gt;was&lt;/em&gt; on a direct connection.&lt;/p&gt;

&lt;p&gt;And it still took about &lt;strong&gt;six minutes&lt;/strong&gt; to load the file.&lt;/p&gt;

&lt;p&gt;So we measured my connection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;0.65 MB/s upload.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;About 5 Mbit/s.&lt;/p&gt;

&lt;p&gt;Every worker in this farm was reading the .blend from my laptop.&lt;/p&gt;

&lt;p&gt;Which means every byte, direct or relayed, had to squeeze out through my upload.&lt;/p&gt;

&lt;p&gt;At 0.65 MB/s, one 115 MB read takes about three minutes.&lt;/p&gt;

&lt;p&gt;Two workers reading at once, about six.&lt;/p&gt;

&lt;p&gt;That worker took in 136 MB in roughly six minutes, about 0.38 MB/s, which is exactly what half of my upload looks like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsw5jzr3skue20nzqng19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsw5jzr3skue20nzqng19.png" alt="Waiting skeleton meme: remote worker loading the 115 MB .blend over my 5 Mbit/s upload"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fixing the hard NAT would have shortened the detour.&lt;/p&gt;

&lt;p&gt;It would not have widened the pipe.&lt;/p&gt;

&lt;p&gt;I switched to a faster Wi-Fi network for one more run, which bumped my upload to about 2.15 MB/s, and the remote first loads dropped to about a minute and a half.&lt;/p&gt;

&lt;p&gt;Better.&lt;/p&gt;

&lt;p&gt;But in that same run my laptop alone finished four of the six chunks while the remote workers were still loading.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why we pulled the plug
&lt;/h2&gt;

&lt;p&gt;For a remote worker to help, two things have to be true.&lt;/p&gt;

&lt;p&gt;It has to get the scene fast, and it has to render fast once it has it.&lt;/p&gt;

&lt;p&gt;Ours had neither.&lt;/p&gt;

&lt;p&gt;The scene came through my home upload, partly via a relay, and the laptops that did load it were either on the wrong GPU, in WSL with no GPU, or tripping over SMB on Windows.&lt;/p&gt;

&lt;p&gt;Every fix we found was real, and every one of them was its own project.&lt;/p&gt;

&lt;p&gt;Copy the project to every laptop ahead of time and lose the "one shared folder" simplicity that made the setup appealing.&lt;/p&gt;

&lt;p&gt;Fight every home router.&lt;/p&gt;

&lt;p&gt;Fix every laptop's GPU setup.&lt;/p&gt;

&lt;p&gt;Make sure every laptop stays awake, plugged in and on Wi-Fi for ten hours.&lt;/p&gt;

&lt;p&gt;So at 9:52 pm we shut it all down.&lt;/p&gt;

&lt;p&gt;Flamenco manager and worker stopped, Samba stopped and disabled, the Tailscale forward turned off, Tailscale itself stopped.&lt;/p&gt;

&lt;p&gt;Nothing deleted, just switched off.&lt;/p&gt;

&lt;p&gt;A few things I would tell past me before this Saturday:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A render farm is a file distribution problem wearing a GPU costume.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The render was the easy part.&lt;/p&gt;

&lt;p&gt;Getting the same 130 MB to every machine, reliably, was the whole job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure your upload before you build anything.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Five minutes with a speed test would have predicted most of this evening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Direct" is about the route, not the speed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A perfect peer-to-peer tunnel is still capped by the slowest pipe on either end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check which GPU Blender is actually using.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Dual-GPU laptops will quietly hand you the integrated chip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flamenco is fine.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It assumes a studio LAN with a NAS, and we gave it four home connections and a laptop pretending to be a NAS.&lt;/p&gt;
&lt;h2&gt;
  
  
  So did Miles ever get his 1080p?
&lt;/h2&gt;

&lt;p&gt;We really wanted this one to work.&lt;/p&gt;

&lt;p&gt;Four laptops, one Saturday, our own little render farm.&lt;/p&gt;

&lt;p&gt;It is a great story, it just is not a fast one.&lt;/p&gt;

&lt;p&gt;But the film still needs rendering, and we did eventually crack it, by spreading the work across multiple providers instead of multiple laptops.&lt;/p&gt;

&lt;p&gt;That is the next post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Follow along so you do not miss how we finally got Miles rendered.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And I would love to hear from you in the comments.&lt;/p&gt;

&lt;p&gt;Have you ever tried to farm out a render, or any big compute job, across machines you did not own?&lt;/p&gt;



&lt;p&gt;&lt;em&gt;&lt;br&gt;
Your team's attention is limited, and the deluge of AI-generated code is making it harder to keep production secure and reliable without slowing you down.&lt;/em&gt;&lt;/p&gt;
&lt;em&gt;

&lt;p&gt;I'm building &lt;strong&gt;LiveReview&lt;/strong&gt;, a blast-radius aware AI code review built for your business-critical systems.&lt;/p&gt;

&lt;p&gt;Instead of presenting every diff with equal emphasis, &lt;strong&gt;LiveReview scores each change by blast radius — how far its impact reaches through your call graph — so you can focus attention where it actually matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spend code review effort where business risk is highest — not spread evenly across every diff.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;
        LiveReview
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Risk-Aware AI Code Review for Business-Critical Systems
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/png/logo-with-text.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fpng%2Flogo-with-text.png" alt="LiveReview" height="80"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml" rel="noopener noreferrer"&gt;&lt;img alt="mcp-testcases.yml" title="mcp-testcases.yml: MCP integration test suite" src="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml/badge.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LiveReview: Risk-Aware AI Code Review for Business-Critical Systems&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;LiveReview is built for engineering teams that ship often, but also care about keeping production stable for customers. It approves some 40-50% of low-risk changes automatically, and it gives your team the tools to handle the rest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand risky changes faster.&lt;/li&gt;
&lt;li&gt;Fix problems in those changes, with human attention directed to the work that needs it.&lt;/li&gt;
&lt;li&gt;Enforce your engineering standards.&lt;/li&gt;
&lt;li&gt;Learn from production outcomes, such as incidents, rollbacks, and queue wait times.&lt;/li&gt;
&lt;li&gt;Improve the review process over time and make it more efficient.&lt;/li&gt;
&lt;li&gt;Connect to the major engineering tools that your team already uses.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Auto-Approve: Ship the Safe 50% Without a Human&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Auto-Approve Upto 50% of Your Pull Requests, Safely.&lt;/strong&gt; Risk scoring decides which MRs/PRs are safe enough to ship without a human, so your team only spends review time on risky and critical changes.&lt;/p&gt;


  
    
    &lt;span class="m-1"&gt;demo.mp4&lt;/span&gt;
    
  

  

  


&lt;p&gt;&lt;i&gt;LiveReview auto-approving a low-risk change while&lt;/i&gt;…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;b&gt;Click below to try LiveReview with your codebase:&lt;/b&gt;&lt;/p&gt;

&lt;/em&gt;&lt;p&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hexmos.com/livereview" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmatye5a3kc7c9w0ckfv8.png" alt="LiveReview Banner"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blender</category>
      <category>networking</category>
      <category>tailscale</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Stack I'd Need for Claude to Direct a Whole YouTube Video in Blender</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Fri, 09 Oct 2026 18:10:15 +0000</pubDate>
      <link>https://dev.to/lovestaco/the-stack-id-need-for-claude-to-direct-a-whole-youtube-video-in-blender-2ekd</link>
      <guid>https://dev.to/lovestaco/the-stack-id-need-for-claude-to-direct-a-whole-youtube-video-in-blender-2ekd</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. &lt;a href="https://github.com/HexmosTech/LiveReview/" rel="noopener noreferrer"&gt;Star us&lt;/a&gt; to help devs discover the project, give it a try, and share your feedback to help improve the product.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two posts ago, I had never opened Blender.&lt;/p&gt;

&lt;p&gt;Since then, Miles Morales has swung on a web, yanked a DEV board into frame, and &lt;a href="https://dev.to/lovestaco/spider-man-moonwalked-off-a-billboard-and-only-my-test-suite-noticed-46k7"&gt;moonwalked 6.5 metres off the edge of a billboard&lt;/a&gt; without anyone noticing.&lt;/p&gt;

&lt;p&gt;Every one of those shots was me and Claude Code, pair programming a 3D scene one prompt at a time.&lt;/p&gt;

&lt;p&gt;It works.&lt;/p&gt;

&lt;p&gt;It is also slow, because I am still the one holding the whole movie in my head.&lt;/p&gt;

&lt;p&gt;So this post is about the next question, the one I keep circling back to at 1 a.m.:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would it actually take for me to type one paragraph and get a finished YouTube video out the other end?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not "Claude helps me in Blender".&lt;/p&gt;

&lt;p&gt;Claude &lt;em&gt;directs&lt;/em&gt; the thing.&lt;/p&gt;

&lt;p&gt;I went digging through every MCP server, Claude Code skill, mocap source and audio model I could find, checked the GitHub stars so we know what is battle tested and what is a weekend project, and wrote down the stack I would build.&lt;/p&gt;

&lt;p&gt;Spoiler: Blender is maybe a third of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The job description: director, not animator
&lt;/h2&gt;

&lt;p&gt;The single most useful thing I learned in &lt;a href="https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7"&gt;part one&lt;/a&gt; was not about rigging.&lt;/p&gt;

&lt;p&gt;It was about where the LLM is good and where it is hopeless.&lt;/p&gt;

&lt;p&gt;Claude was great at writing &lt;code&gt;bpy&lt;/code&gt; code, relinking broken textures, sequencing clips on a timeline, and stitching root motion between them.&lt;/p&gt;

&lt;p&gt;Claude was never once asked to invent how a human body lands from a jump.&lt;/p&gt;

&lt;p&gt;That came from Mixamo, which came from a real performer in a mocap suit.&lt;/p&gt;

&lt;p&gt;And that split is the whole architecture.&lt;/p&gt;

&lt;p&gt;An LLM is a brilliant writer of structure and code, a decent critic of a still image, and a terrible source of biomechanics.&lt;/p&gt;

&lt;p&gt;So every part of a video should go to whoever is actually good at it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwaaa81su3k8vh6sk5v97.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwaaa81su3k8vh6sk5v97.png" alt="Three columns: Claude writes the shot list, scripts, camera moves and the edit; Claude fetches body motion, sets, the character and mouth shapes; Claude orders voice, SFX, music and background props from specialist models" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If it moves like a human, fetch it.&lt;/p&gt;

&lt;p&gt;If it is code, write it.&lt;/p&gt;

&lt;p&gt;If it is a sound, order it from a model that does nothing but sound.&lt;/p&gt;

&lt;p&gt;Here is the whole stack with that rule applied:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    ME[One paragraph from me] --&amp;gt; DIR[Claude Code&amp;lt;br/&amp;gt;director skill]
    DIR --&amp;gt; SL[Shot list&amp;lt;br/&amp;gt;YAML]
    SL --&amp;gt; BR[Blender bridge&amp;lt;br/&amp;gt;one MCP server]
    SL --&amp;gt; MO[Motion library&amp;lt;br/&amp;gt;Mixamo, CMU, Rokoko]
    SL --&amp;gt; AS[Assets&amp;lt;br/&amp;gt;Poly Haven, Sketchfab, image to 3D]
    SL --&amp;gt; AU[Audio&amp;lt;br/&amp;gt;TTS, SFX, music]
    AU --&amp;gt; LS[Rhubarb&amp;lt;br/&amp;gt;mouth shapes]
    MO --&amp;gt; BR
    AS --&amp;gt; BR
    LS --&amp;gt; BR
    BR --&amp;gt; BL[Blender, headless&amp;lt;br/&amp;gt;EEVEE drafts, frame sequences]
    BL --&amp;gt; QA{Checks pass?&amp;lt;br/&amp;gt;eyes + asserts}
    QA --&amp;gt;|no| DIR
    QA --&amp;gt;|yes| FF[FFmpeg&amp;lt;br/&amp;gt;frames + stems]
    AU --&amp;gt; FF
    FF --&amp;gt; YT[YouTube-ready MP4]

    classDef human fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
    classDef brain fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a
    classDef fetch fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef order fill:#ff9a5c,stroke:#c4602a,color:#1a1a1a
    classDef engine fill:#6ea8ff,stroke:#2f62c4,color:#1a1a1a
    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a

    class ME,YT human
    class DIR,SL brain
    class MO,AS,LS fetch
    class AU order
    class BR,BL,FF engine
    class QA decision&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Let's walk it bottom up, starting with the piece everyone argues about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: the Blender bridge (pick exactly one)
&lt;/h2&gt;

&lt;p&gt;This is the MCP server that lets Claude reach into a running Blender and press buttons by writing Python.&lt;/p&gt;

&lt;p&gt;There are now a lot of them.&lt;/p&gt;

&lt;p&gt;Here are the four I looked at seriously, with stars as of this week:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bridge&lt;/th&gt;
&lt;th&gt;What it is good at&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://github.com/ahujasid/mcp-for-blender" rel="noopener noreferrer"&gt;mcp-for-blender&lt;/a&gt; ⭐ (~30.2k) (formerly &lt;code&gt;blender-mcp&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;The one I used in parts 1 and 2. Code execution, viewport screenshots, plus Poly Haven, Sketchfab, Poly Pizza, Hyper3D Rodin, Hunyuan3D and Tripo built in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://www.blender.org/lab/mcp-server/" rel="noopener noreferrer"&gt;Official Blender MCP&lt;/a&gt; ⭐ by Blender Lab&lt;/td&gt;
&lt;td&gt;Understanding a scene: explaining node setups, finding what uses a material, cleanup. Blender's page lists 5.1 or newer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://github.com/carlosh7/blender-mcp" rel="noopener noreferrer"&gt;carlosh7/blender-mcp&lt;/a&gt; ⭐ (8 stars)&lt;/td&gt;
&lt;td&gt;248 tools across 14 categories, 371 commits, 479 passing tests, MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://github.com/minihellboy/claude-blender" rel="noopener noreferrer"&gt;claude-blender&lt;/a&gt; ⭐ (5 stars)&lt;/td&gt;
&lt;td&gt;Undo checkpoints, camera auto-framing, local TripoSR and Shap-E generation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The official one is official in the sense that Blender developers built it, and &lt;a href="https://academy.claude.com/tutorials/using-the-blender-connector-in-claude" rel="noopener noreferrer"&gt;Anthropic announced it&lt;/a&gt; as a Claude connector this year.&lt;/p&gt;

&lt;p&gt;Read its own page though, and it is pitched as a way to &lt;em&gt;understand&lt;/em&gt; complex setups, not to churn out a film.&lt;/p&gt;

&lt;p&gt;It also carries the most honest warning I have seen on any MCP page: the server "will execute LLM generated code in Blender without any guards in place", and Blender suggests a VM or a machine without sensitive data on it.&lt;/p&gt;

&lt;p&gt;That applies to every server in the table, by the way.&lt;/p&gt;

&lt;p&gt;They all ship some flavour of "run this Python", and that is the tool doing the real work.&lt;/p&gt;

&lt;p&gt;Which brings me to the 248 tool one.&lt;/p&gt;

&lt;p&gt;It is clearly a lot of careful work, with real tests and CI.&lt;/p&gt;

&lt;p&gt;But it made me realise that &lt;strong&gt;tool count is not a feature&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp35q0tnnsofch3nw1f86.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp35q0tnnsofch3nw1f86.png" alt="American Chopper argument meme about 248 MCP tools versus one tool that runs Python" width="360" height="1012"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every tool an MCP server exposes comes with a schema: a name, a description, a JSON shape for its arguments.&lt;/p&gt;

&lt;p&gt;Many clients put all of those in the context window on every single turn, whether you use them or not.&lt;/p&gt;

&lt;p&gt;Some, Claude Code included, can defer them and search on demand, which fixes the space problem.&lt;/p&gt;

&lt;p&gt;It does not fix the &lt;em&gt;picking&lt;/em&gt; problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7dfn3megrcnxxmb6j9jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7dfn3megrcnxxmb6j9jq.png" alt="A context window mostly filled by tool schemas versus one mostly free with skill descriptions, plus five near-identical tool names for moving a cube" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If there are five slightly different ways to move a cube, the model has to choose one, and every choice is a chance to choose wrong.&lt;/p&gt;

&lt;p&gt;One &lt;code&gt;execute_blender_code&lt;/code&gt; and a model that writes good &lt;code&gt;bpy&lt;/code&gt; already covers all 248, because every button in Blender is a thin skin over &lt;code&gt;bpy&lt;/code&gt; anyway.&lt;/p&gt;

&lt;p&gt;So my pick is boring: &lt;strong&gt;mcp-for-blender as the one bridge&lt;/strong&gt;, installed with &lt;code&gt;claude mcp add blender uvx mcp-for-blender&lt;/code&gt;, and the official one kept around for "why is this scene slow" archaeology.&lt;/p&gt;

&lt;p&gt;And I am stealing the best idea from the 5 star repo without installing it.&lt;/p&gt;

&lt;p&gt;Checkpoints before risky edits are a great idea.&lt;/p&gt;

&lt;p&gt;In part two the &lt;code&gt;.blend&lt;/code&gt; file became a build artifact generated by scripts, so my checkpoints are just &lt;code&gt;git commit&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: skills, not more tools
&lt;/h2&gt;

&lt;p&gt;If tools are expensive, how do you teach Claude cinematography?&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://code.claude.com/docs/en/skills" rel="noopener noreferrer"&gt;Claude Code skills&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A skill is a folder in &lt;code&gt;.claude/skills/&lt;/code&gt; with a &lt;code&gt;SKILL.md&lt;/code&gt; inside: a one line description that is always visible, and a body of instructions and scripts that only loads when the task needs it.&lt;/p&gt;

&lt;p&gt;It is the lazy import of prompting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/kevinbadi/blender-skills" rel="noopener noreferrer"&gt;blender-skills&lt;/a&gt; (~143 stars) is a good example of the idea.&lt;/p&gt;

&lt;p&gt;It has camera moves as skills, &lt;code&gt;turntable&lt;/code&gt;, &lt;code&gt;slow-zoom&lt;/code&gt;, &lt;code&gt;dolly-rotate&lt;/code&gt;, &lt;code&gt;crane-shot&lt;/code&gt;, &lt;code&gt;perfect-loop&lt;/code&gt;, plus image to 3D through Meshy and a toolkit that does Mixamo retargeting.&lt;/p&gt;

&lt;p&gt;It is aimed at product shots, so I would not install it wholesale for a superhero short.&lt;/p&gt;

&lt;p&gt;I would copy its shape.&lt;/p&gt;

&lt;p&gt;The thing I actually want is a small library of &lt;strong&gt;film vocabulary&lt;/strong&gt;, each word backed by tested code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;establishing_wide&lt;/code&gt;, &lt;code&gt;push_in&lt;/code&gt;, &lt;code&gt;pull_out&lt;/code&gt;, &lt;code&gt;orbit&lt;/code&gt;, &lt;code&gt;crane_up&lt;/code&gt;, &lt;code&gt;tracking&lt;/code&gt;, &lt;code&gt;over_the_shoulder&lt;/code&gt;, &lt;code&gt;handheld_shake&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rainy_night&lt;/code&gt;, &lt;code&gt;golden_hour&lt;/code&gt;, &lt;code&gt;villain_red&lt;/code&gt;, &lt;code&gt;neon_street&lt;/code&gt;, &lt;code&gt;soft_closeup_key&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then "start wide, track him to the ledge, push in slowly as he says the line" stops being a creative writing exercise for the model.&lt;/p&gt;

&lt;p&gt;It becomes three skill calls with numbers.&lt;/p&gt;

&lt;p&gt;Same idea applies to lighting.&lt;/p&gt;

&lt;p&gt;A preset that is right once is right forever, and nobody has to rediscover three point lighting at 2 a.m. because Miles's suit rendered as a black blob again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: motion, fetched not generated
&lt;/h2&gt;

&lt;p&gt;This is where most "AI made my animation" demos fall apart, and it is the layer I am most opinionated about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not let the LLM keyframe a human.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It will produce something that technically moves and spiritually does not.&lt;/p&gt;

&lt;p&gt;Real motion has weight shifts, overshoot, a little settle at the end of a landing.&lt;/p&gt;

&lt;p&gt;Mocap has all of that for free, because a person did it.&lt;/p&gt;

&lt;p&gt;The sources I would wire in, in order of effort:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.mixamo.com" rel="noopener noreferrer"&gt;Mixamo&lt;/a&gt;.&lt;/strong&gt; Free, auto-rigs a T-posed character from five markers, thousands of clips. I downloaded 78 of them one click at a time in part one, and I would do it again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="http://mocap.cs.cmu.edu/" rel="noopener noreferrer"&gt;CMU Motion Capture Database&lt;/a&gt;.&lt;/strong&gt; About 2,500 free clips, with a &lt;a href="https://github.com/una-dinosauria/cmu-mocap" rel="noopener noreferrer"&gt;BVH mirror on GitHub&lt;/a&gt; (~203 stars). Needs a retargeter, which part one built and part two debugged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.rokoko.com/integrations/blender" rel="noopener noreferrer"&gt;Rokoko&lt;/a&gt;.&lt;/strong&gt; Their Blender add-on is free and does retargeting. Their free plan includes Rokoko Vision, single camera AI mocap. That last one is the exciting bit: the motion Mixamo does not have, I can act out in my living room with a webcam.&lt;/p&gt;

&lt;p&gt;And then the controls.&lt;/p&gt;

&lt;p&gt;In part one I dragged Miles's hips down and his whole body sank through the floor like a ghost, because the rig had bones but no IK.&lt;/p&gt;

&lt;p&gt;Two fixes, depending on budget:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://extensions.blender.org/add-ons/mixamo-rig/" rel="noopener noreferrer"&gt;Mixamo Rig&lt;/a&gt;&lt;/strong&gt; is a free, GPL extension that builds a proper IK control rig on top of a Mixamo skeleton and bakes animation in and out of it. Check the version range on the listing before you build on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://superhivemarket.com/products/auto-rig-pro" rel="noopener noreferrer"&gt;Auto-Rig Pro&lt;/a&gt;&lt;/strong&gt; by Artell is the paid, serious option, about $50, used in shipped games like Manor Lords, with rigging, retargeting between skeletons and facial rigs in one place.&lt;/p&gt;

&lt;p&gt;If I spend money anywhere in this stack, it is here.&lt;/p&gt;

&lt;p&gt;Rigging is the one job where a mistake quietly poisons every shot that comes after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4: the face, or the close-up problem
&lt;/h2&gt;

&lt;p&gt;Here is a thing I did not appreciate until I tried to frame a close-up.&lt;/p&gt;

&lt;p&gt;In a wide shot, nobody looks at the face.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0jqdpc3z5eqc0yvrn41m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0jqdpc3z5eqc0yvrn41m.png" alt="See? Nobody Cares meme about a broken face rig in a wide shot" width="360" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then the camera pushes in for the line, and the face is the &lt;em&gt;only&lt;/em&gt; thing on screen.&lt;/p&gt;

&lt;p&gt;A mouth flapping open and closed on a timer reads instantly as fake.&lt;/p&gt;

&lt;p&gt;So does a character who never blinks, or whose eyes are locked dead ahead like a mannequin's.&lt;/p&gt;

&lt;p&gt;The good news is that most of facial performance can be fetched too, and the most important piece is wonderfully small.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/DanielSWolf/rhubarb-lip-sync" rel="noopener noreferrer"&gt;Rhubarb Lip Sync&lt;/a&gt; (~2.6k stars) is a command line tool that listens to a voice recording and writes out a timeline of mouth shapes.&lt;/p&gt;

&lt;p&gt;It uses nine shapes, A to H plus X for rest, the same small set classic cartoon studios used, and you can hand it the script with &lt;code&gt;--dialogFile&lt;/code&gt; so it knows the words instead of guessing.&lt;/p&gt;

&lt;p&gt;It says it is built for 2D, but the output is just JSON with timestamps, and a 3D face can use it just as well.&lt;/p&gt;

&lt;p&gt;There is a &lt;a href="https://github.com/scaredyfish/blender-rhubarb-lipsync" rel="noopener noreferrer"&gt;Blender add-on&lt;/a&gt; for it (~228 stars), but it has not been touched since 2023.&lt;/p&gt;

&lt;p&gt;The glue is short enough that I would rather have Claude own it:&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;# rhubarb -f json -d line.txt -o line.json line.wav
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;bpy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;cues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line.json&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;mouthCues&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bpy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Miles_Head&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape_keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key_blocks&lt;/span&gt;
&lt;span class="n"&gt;fps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bpy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;   &lt;span class="c1"&gt;# line begins on frame 120
&lt;/span&gt;
&lt;span class="c1"&gt;# Rhubarb's letters -&amp;gt; this model's shape key names
&lt;/span&gt;&lt;span class="n"&gt;VISEME&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;A&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;MBP&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;B&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;EE&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;C&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;EH&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;D&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;AA&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;E&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;OH&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;F&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;OO&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;G&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;FV&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;H&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;L&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;X&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;rest&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;cue&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;fps&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;shape&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;VISEME&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;VISEME&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cue&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
        &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;keyframe_insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is the whole trick: audio in, a keyframe per mouth change out.&lt;/p&gt;

&lt;p&gt;Blender's default Bezier interpolation eases between the keys, so the mouth glides instead of snapping.&lt;/p&gt;

&lt;p&gt;If your model drives its mouth with face bones instead of shape keys, like the 143 bone face rig on my Miles, the same loop poses bones instead.&lt;/p&gt;

&lt;p&gt;Blinks and eye darts are even cheaper.&lt;/p&gt;

&lt;p&gt;A blink every two to six seconds, randomised, and a tiny eye flick toward whatever the character is reacting to, is a dozen lines of procedural keyframing, and it is the difference between a person and a wax figure.&lt;/p&gt;

&lt;p&gt;Notice the dependency hiding here, too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lip sync needs the audio to exist first.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Which means the voice is not post production.&lt;/p&gt;

&lt;p&gt;It is pre production.&lt;/p&gt;
&lt;h2&gt;
  
  
  Layer 5: assets, mostly fetched
&lt;/h2&gt;

&lt;p&gt;Same rule again: generate only what nobody has made yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://polyhaven.com" rel="noopener noreferrer"&gt;Poly Haven&lt;/a&gt;.&lt;/strong&gt; Thousands of CC0 HDRIs, textures and models. CC0 means no attribution and no strings. mcp-for-blender searches and imports from it directly, and an HDRI alone fixes half of "this scene looks fake".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://sketchfab.com" rel="noopener noreferrer"&gt;Sketchfab&lt;/a&gt;.&lt;/strong&gt; Huge, and also where my Miles came from. Read every license, because they vary model to model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image or text to 3D&lt;/strong&gt;, for the props nobody has made:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/Tencent-Hunyuan/Hunyuan3D-2" rel="noopener noreferrer"&gt;Hunyuan3D-2&lt;/a&gt; (~15k stars) and &lt;a href="https://github.com/Tencent-Hunyuan/Hunyuan3D-2.1" rel="noopener noreferrer"&gt;Hunyuan3D-2.1&lt;/a&gt; (~4.1k stars, adds PBR materials), from Tencent, runnable locally if your GPU is braver than my 4 GB GTX 1650&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/VAST-AI-Research/TripoSR" rel="noopener noreferrer"&gt;TripoSR&lt;/a&gt; (~7k stars), fast single image reconstruction&lt;/li&gt;
&lt;li&gt;Hosted options like Hyper3D Rodin and Tripo, already wired into mcp-for-blender&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One honest caveat from someone who just learned what skin weights are.&lt;/p&gt;

&lt;p&gt;Generated meshes are usually a dense soup of triangles.&lt;/p&gt;

&lt;p&gt;That is fine for a mailbox in the background.&lt;/p&gt;

&lt;p&gt;It is not fine for anything that has to &lt;em&gt;bend&lt;/em&gt;, because rigging wants clean edge loops around elbows and knees, and a triangle soup deforms like wet cardboard.&lt;/p&gt;

&lt;p&gt;So: generated props, sourced or hand built heroes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Layer 6: audio is half the movie
&lt;/h2&gt;

&lt;p&gt;Every engineer I know who tries video makes the same mistake I did.&lt;/p&gt;

&lt;p&gt;They spend three days on render settings and zero minutes on sound.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6eqrn93pxbztu777n1gu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6eqrn93pxbztu777n1gu.png" alt="This Is Where I'd Put My Trophy meme: this is where I'd put my sound design, if I had any" width="360" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Put a whoosh on a jump, a &lt;em&gt;thwip&lt;/em&gt; on a web shot and a low thud on the landing, and the exact same frames suddenly look like they cost ten times more.&lt;/p&gt;

&lt;p&gt;The trick is to treat audio as &lt;strong&gt;separate stems&lt;/strong&gt;, never one mixed track:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dialogue.&lt;/strong&gt; &lt;a href="https://elevenlabs.io" rel="noopener noreferrer"&gt;ElevenLabs&lt;/a&gt; for character voices. Generate it first, because the face needs it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SFX and ambience.&lt;/strong&gt; &lt;a href="https://elevenlabs.io/docs/overview/capabilities/sound-effects" rel="noopener noreferrer"&gt;ElevenLabs sound effects&lt;/a&gt; turns a text description into a sound, up to 30 seconds per generation, with a seamless loop mode for rain and city hum, and 48 kHz WAV for one shots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Music.&lt;/strong&gt; Its own model, its own stem. &lt;a href="https://github.com/Stability-AI/stable-audio-tools" rel="noopener noreferrer"&gt;stable-audio-tools&lt;/a&gt; (~3.9k stars) is Stability's open code for their audio models if you want local control. Check the license of whatever you pick before it goes on a monetised channel.&lt;/p&gt;

&lt;p&gt;Separate stems are what make the edit programmable.&lt;/p&gt;

&lt;p&gt;Music too loud under the line? Change one number.&lt;/p&gt;

&lt;p&gt;Want the music to dip automatically whenever someone talks? That is FFmpeg's &lt;code&gt;sidechaincompress&lt;/code&gt; filter, keyed off the dialogue stem.&lt;/p&gt;

&lt;p&gt;None of that is possible once everything is baked into one WAV.&lt;/p&gt;
&lt;h2&gt;
  
  
  Layer 7: render like a build system, assemble with FFmpeg
&lt;/h2&gt;

&lt;p&gt;Part two already turned the scene into a build artifact, so this layer is mostly "keep doing that, at movie scale".&lt;/p&gt;

&lt;p&gt;Three rules:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Render headless.&lt;/strong&gt; &lt;code&gt;blender -b&lt;/code&gt; with no UI, driven by scripts, so a render is a command, not a person clicking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Render frame sequences, not MP4s.&lt;/strong&gt; If Blender crashes on frame 1,400 of a movie file, you lose the movie. If it crashes on frame 1,400 of a PNG sequence, you lose one frame and resume. &lt;a href="https://docs.blender.org/manual/en/latest/render/output/animation.html" rel="noopener noreferrer"&gt;Blender's own docs&lt;/a&gt; recommend exactly this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Draft at 360p, final once.&lt;/strong&gt; Part two's &lt;code&gt;--fast&lt;/code&gt; mode, with ray tracing, motion blur and extra samples switched off, roughly halved frame time. Everything is reviewed in drafts.&lt;/p&gt;

&lt;p&gt;Then &lt;a href="https://github.com/FFmpeg/FFmpeg" rel="noopener noreferrer"&gt;FFmpeg&lt;/a&gt; (~64.9k stars, and it is only a mirror) glues frames and stems into a video:&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;# render shot 30 as a PNG sequence (flags before -a, Blender reads them in order)&lt;/span&gt;
blender &lt;span class="nt"&gt;-b&lt;/span&gt; build/sh030.blend &lt;span class="nt"&gt;-o&lt;/span&gt; //frames/sh030_#### &lt;span class="nt"&gt;-F&lt;/span&gt; PNG &lt;span class="nt"&gt;-s&lt;/span&gt; 1 &lt;span class="nt"&gt;-e&lt;/span&gt; 96 &lt;span class="nt"&gt;-a&lt;/span&gt;

&lt;span class="c"&gt;# frames + three stems -&amp;gt; one mp4, music ducked under the dialogue&lt;/span&gt;
ffmpeg &lt;span class="nt"&gt;-framerate&lt;/span&gt; 30 &lt;span class="nt"&gt;-i&lt;/span&gt; frames/sh030_%04d.png &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; audio/dialogue.wav &lt;span class="nt"&gt;-i&lt;/span&gt; audio/sfx.wav &lt;span class="nt"&gt;-i&lt;/span&gt; audio/music.wav &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-filter_complex&lt;/span&gt; &lt;span class="s2"&gt;"[1:a]asplit[d][key];[3:a][key]sidechaincompress=threshold=0.05:ratio=8[m];&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
[d][2:a][m]amix=inputs=3:normalize=0[a]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-map&lt;/span&gt; 0:v &lt;span class="nt"&gt;-map&lt;/span&gt; &lt;span class="s2"&gt;"[a]"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;:v libx264 &lt;span class="nt"&gt;-pix_fmt&lt;/span&gt; yuv420p &lt;span class="nt"&gt;-crf&lt;/span&gt; 18 &lt;span class="nt"&gt;-c&lt;/span&gt;:a aac sh030.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;asplit&lt;/code&gt; makes two copies of the dialogue, one to hear and one to act as the key that pushes the music down.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;normalize=0&lt;/code&gt; stops &lt;code&gt;amix&lt;/code&gt; from quietly turning everything down to make room, which is a fun one to debug by ear.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-pix_fmt yuv420p&lt;/code&gt; is there because some players will happily show a black screen without it.&lt;/p&gt;

&lt;p&gt;I would still do the final colour pass and titles in &lt;a href="https://www.blackmagicdesign.com/products/davinciresolve" rel="noopener noreferrer"&gt;DaVinci Resolve&lt;/a&gt; for anything going on my channel, but a machine should be able to produce the rough cut alone.&lt;/p&gt;
&lt;h2&gt;
  
  
  Layer 8: two reviewers, because one of them is blind
&lt;/h2&gt;

&lt;p&gt;The loop that made parts one and two work was Claude grabbing a screenshot, looking at it and fixing what it saw.&lt;/p&gt;

&lt;p&gt;That loop is great.&lt;/p&gt;

&lt;p&gt;It is also how a moonwalk got shipped.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbrongda8agwpcbc2mpu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbrongda8agwpcbc2mpu5.png" alt="Two reviewers: Claude looking at a frame catches dark faces, wide lenses and bad framing; a 44-line script catches pops, floating feet and characters standing past the edge of a billboard" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vision models review a still picture.&lt;/p&gt;

&lt;p&gt;Motion bugs live &lt;em&gt;between&lt;/em&gt; the frames.&lt;/p&gt;

&lt;p&gt;The 22 cm pop in the middle of a crossfade, the single frame where Miles hovered at a cut, the stance wider than the ledge he stood on, none of those survive a look at frame 1,201.&lt;/p&gt;

&lt;p&gt;They all fail an assertion that measures where the feet are on every frame.&lt;/p&gt;

&lt;p&gt;So every shot gets both reviewers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Eyes&lt;/strong&gt;: Claude looks at a contact sheet of draft frames and judges framing, lighting, mood and readability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asserts&lt;/strong&gt;: scripts measure contact, bounds, continuity at cuts and missing textures, and fail loudly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the asserts fail, nobody wastes time looking.&lt;/p&gt;
&lt;h2&gt;
  
  
  The missing piece: a director skill
&lt;/h2&gt;

&lt;p&gt;Everything above is a department.&lt;/p&gt;

&lt;p&gt;What turns departments into a studio is one skill that takes my paragraph and writes the plan everyone else works from.&lt;/p&gt;

&lt;p&gt;That plan should be data, not prose:&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;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rooftop_siren&lt;/span&gt;
&lt;span class="na"&gt;fps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
&lt;span class="na"&gt;character&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;original_hero_v2&lt;/span&gt;       &lt;span class="c1"&gt;# not Miles, see above&lt;/span&gt;
&lt;span class="na"&gt;shots&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SH010&lt;/span&gt;
    &lt;span class="na"&gt;seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
    &lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rooftop_night_rain&lt;/span&gt;
    &lt;span class="na"&gt;camera&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;establishing_wide&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;push_in&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;motion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;clip&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Standing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Idle"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;ambience&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rain&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rooftop,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;distant&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;traffic,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;wind"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SH020&lt;/span&gt;
    &lt;span class="na"&gt;seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.5&lt;/span&gt;
    &lt;span class="na"&gt;camera&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;extreme_closeup&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;motion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;clip&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Look&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Around"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;turn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;30&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;dialogue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;voice&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;hero&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;hear&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;that?"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;face&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;lipsync&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;rhubarb&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;blinks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;auto&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;look_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;siren&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SH030&lt;/span&gt;
    &lt;span class="na"&gt;seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3.2&lt;/span&gt;
    &lt;span class="na"&gt;camera&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;tracking&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;low_angle&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;motion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;clip&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Jump&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Down"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;speed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;1.2&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;audio&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;sfx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cloth&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;whoosh"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;thwip"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heavy&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;landing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;concrete"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Look at what is in there.&lt;/p&gt;

&lt;p&gt;Every value is a skill name, a library lookup or a prompt for an audio model.&lt;/p&gt;

&lt;p&gt;Nothing asks the LLM to invent a joint angle, and nothing asks it to remember the whole movie at once.&lt;/p&gt;

&lt;p&gt;The director writes this once.&lt;/p&gt;

&lt;p&gt;Then each shot runs through the same loop, and a bad shot is re-run on its own instead of the whole film:&lt;br&gt;
&lt;/p&gt;
&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    S[Shot from the YAML] --&amp;gt; A[Generate dialogue&amp;lt;br/&amp;gt;and SFX]
    A --&amp;gt; L[Rhubarb&amp;lt;br/&amp;gt;mouth cues]
    S --&amp;gt; M[Fetch motion&amp;lt;br/&amp;gt;and set]
    L --&amp;gt; B[Build shot&amp;lt;br/&amp;gt;bpy scripts]
    M --&amp;gt; B
    B --&amp;gt; D[360p draft]
    D --&amp;gt; T{Asserts pass?}
    T --&amp;gt;|no| B
    T --&amp;gt;|yes| V{Claude likes&amp;lt;br/&amp;gt;the contact sheet?}
    V --&amp;gt;|no, fix camera or light| B
    V --&amp;gt;|yes| F[Final frames]
    F --&amp;gt; X[FFmpeg mux]

    classDef src fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
    classDef order fill:#ff9a5c,stroke:#c4602a,color:#1a1a1a
    classDef fetch fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef build fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a
    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef out fill:#6ea8ff,stroke:#2f62c4,color:#1a1a1a

    class S src
    class A order
    class L,M fetch
    class B,D build
    class T,V decision
    class F,X out&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;It is CI for a movie.&lt;/p&gt;

&lt;p&gt;Each shot is a job, the YAML is the config, the asserts are the tests, and Claude is the reviewer who also writes the fixes.&lt;/p&gt;
&lt;h2&gt;
  
  
  The order I am building it in
&lt;/h2&gt;

&lt;p&gt;Not the order of the diagram, the order of the dependencies:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bridge + skills.&lt;/strong&gt; One MCP server, a camera and lighting skill library. Without reliable shots, nothing else matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Motion library.&lt;/strong&gt; Mixamo and CMU behind one retargeter, Mixamo Rig for IK, Rokoko Vision for the clips nobody has.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audio stems.&lt;/strong&gt; Voice, SFX, music, all separate. This comes &lt;em&gt;before&lt;/em&gt; the face, because the face reads the audio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Face.&lt;/strong&gt; Rhubarb glue, procedural blinks and eye targets, then close-ups stop being scary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asserts.&lt;/strong&gt; Part two's checks, generalised to every shot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Director skill.&lt;/strong&gt; Only once every department works on its own. A director with no crew just writes very confident YAML.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And the question I will be honest about: will this make a Pixar short?&lt;/p&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;What it can make is something I could not make at all a month ago: a 30 second scene with a character who moves like a person, talks with a mouth that matches the words, sounds like it happened somewhere, and was assembled by a machine while I wrote the next paragraph.&lt;/p&gt;

&lt;p&gt;That is a wrap for part three.&lt;/p&gt;

&lt;p&gt;If you have built any piece of this, especially the face, tell me what broke. I clearly have a lot of breaking ahead of me.&lt;/p&gt;



&lt;p&gt;&lt;em&gt;&lt;br&gt;
Your team's attention is limited, and the deluge of AI-generated code is making it harder to keep production secure and reliable without slowing you down.&lt;/em&gt;&lt;/p&gt;
&lt;em&gt;

&lt;p&gt;I'm building &lt;strong&gt;LiveReview&lt;/strong&gt;, a blast-radius aware AI code review built for your business-critical systems.&lt;/p&gt;

&lt;p&gt;Instead of presenting every diff with equal emphasis, &lt;strong&gt;LiveReview scores each change by blast radius — how far its impact reaches through your call graph — so you can focus attention where it actually matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spend code review effort where business risk is highest — not spread evenly across every diff.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;
        LiveReview
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Blast-Radius Aware AI Code Review for Business-Critical Systems
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/png/logo-with-text.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fpng%2Flogo-with-text.png" alt="LiveReview" height="80"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml" rel="noopener noreferrer"&gt;&lt;img alt="mcp-testcases.yml" title="mcp-testcases.yml: MCP integration test suite" src="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml/badge.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LiveReview: Risk-Aware AI Code Review for Business-Critical Systems&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;LiveReview is an AI code reviewer that scores every hunk of a diff by &lt;strong&gt;blast radius&lt;/strong&gt;: how far a change reaches through your call graph, how much persistent state it touches, and how well-tested it is. A 3-line change to a shared auth check can outrank a 300-line UI tweak. Your team's attention goes to the highest-risk code first, not spread evenly across every diff.&lt;/p&gt;


  
    
    &lt;span class="m-1"&gt;blast-radius-demo.mp4&lt;/span&gt;
  

  

  


&lt;p&gt;&lt;i&gt;LiveReview's Blast Radius &amp;amp; Review Priority scoring, live in the diff viewer.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;The exact math, not a black box&lt;/th&gt;

&lt;th&gt;Visualize blast radius at a glance&lt;/th&gt;

&lt;th&gt;Every factor that feeds the score&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-3.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-3.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-4.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-4.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-2.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-2.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;&lt;/p&gt;

How does Blast Radius scoring work? (a more technical explanation)

&lt;p&gt;&lt;strong&gt;Here's the goal:&lt;/strong&gt;&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;A 3-line fix in a function used by 40 other files, that also writes to a database, should score high.&lt;/li&gt;

&lt;li&gt;A 300-line UI change in one file, fully covered by tests…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;b&gt;Click below to try LiveReview with your codebase:&lt;/b&gt;&lt;/p&gt;

&lt;/em&gt;&lt;p&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hexmos.com/livereview" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvls0pq7nymbrll98je6s.png" alt="LiveReview Banner" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blender</category>
      <category>ai</category>
      <category>mcp</category>
      <category>claude</category>
    </item>
    <item>
      <title>Spider-Man Moonwalked Off a Billboard and Only My Test Suite Noticed</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Mon, 05 Oct 2026 20:26:54 +0000</pubDate>
      <link>https://dev.to/lovestaco/spider-man-moonwalked-off-a-billboard-and-only-my-test-suite-noticed-46k7</link>
      <guid>https://dev.to/lovestaco/spider-man-moonwalked-off-a-billboard-and-only-my-test-suite-noticed-46k7</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. &lt;a href="https://github.com/HexmosTech/LiveReview/" rel="noopener noreferrer"&gt;Star us&lt;/a&gt; to help devs discover the project, give it a try, and share your feedback to help improve the product.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Miles Morales moonwalked 6.5 metres past the edge of a billboard.&lt;/p&gt;

&lt;p&gt;Not on the billboard.&lt;/p&gt;

&lt;p&gt;Past it.&lt;/p&gt;

&lt;p&gt;On nothing.&lt;/p&gt;

&lt;p&gt;I had watched that shot more than once and did not notice.&lt;/p&gt;

&lt;p&gt;The viewport did not notice either.&lt;/p&gt;

&lt;p&gt;A 44 line Python script noticed, and it has been catching my mistakes ever since.&lt;/p&gt;

&lt;p&gt;This is part two of my accidental Blender education.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7" class="crayons-story__hidden-navigation-link"&gt;I Made Spider-Man Swing Without Animating a Single Frame&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/lovestaco" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002302%2F9dbe5057-f6da-4c08-9b5d-37fe9d281476.png" alt="lovestaco profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/lovestaco" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Athreya aka Maneshwar
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Athreya aka Maneshwar
                
                
              
              &lt;div id="story-author-preview-content-4793045" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/lovestaco" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002302%2F9dbe5057-f6da-4c08-9b5d-37fe9d281476.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Athreya aka Maneshwar&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 3&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7" id="article-link-4793045"&gt;
          I Made Spider-Man Swing Without Animating a Single Frame
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/blender"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;blender&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;48&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              7&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            13 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;In &lt;a href="https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7"&gt;part one&lt;/a&gt; I went from never having opened Blender to rigging a Spider-Man model, pulling animations from Mixamo and retargeting old Carnegie Mellon mocap onto him.&lt;/p&gt;

&lt;p&gt;This one is about what happens next, when you try to turn that into an actual film and every mistake is suddenly a bug report.&lt;/p&gt;

&lt;h2&gt;
  
  
  The film, and why it is really a build system
&lt;/h2&gt;

&lt;p&gt;The goal is a roughly 60 second explainer for LiveReview.&lt;/p&gt;

&lt;p&gt;A masked Miles web-swings through a city at sunset, and every "slide" is part of the street.&lt;/p&gt;

&lt;p&gt;A wall board, a rooftop billboard, an LED ticker, bus stop and subway lightboxes, a big street screen, and a runaway train for the finale.&lt;/p&gt;

&lt;p&gt;Here is the twist that shapes everything below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nothing in the scene is hand edited.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The whole thing is generated headless by Python scripts against Blender 5.2, rendered with EEVEE on a 4 GB GTX 1650.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;build/street.blend&lt;/code&gt; is not a file I open and tweak.&lt;/p&gt;

&lt;p&gt;It is a build artifact, like a &lt;code&gt;dist/&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;So every fix is a code change plus a rebuild, and the workflow ended up looking a lot more like CI than like art class:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    S[Python scripts] --&amp;gt;|blender -b| B[street.blend]
    B --&amp;gt; C{support check clean?}
    C --&amp;gt;|no| S
    C --&amp;gt;|yes| D[--fast draft render]
    D --&amp;gt; T[review copy with timecode]
    T --&amp;gt; R{looks right?}
    R --&amp;gt;|no, f1201 is off| S
    R --&amp;gt;|yes| G[git commit]
    G --&amp;gt; F[full quality render]

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef code fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a
    classDef build fill:#6ea8ff,stroke:#2f62c4,color:#1a1a1a
    classDef render fill:#ff9a5c,stroke:#c4602a,color:#1a1a1a
    classDef done fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a

    class C,R decision
    class S,G code
    class B build
    class D,T render
    class F done&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Almost every lesson in this post fell out of that loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Can't you just use the GPU to render faster?"
&lt;/h2&gt;

&lt;p&gt;This was my first question once renders started taking a while.&lt;/p&gt;

&lt;p&gt;The answer was a little embarrassing.&lt;/p&gt;

&lt;p&gt;It already was.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frmroyuhttb83qxlmfs7j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frmroyuhttb83qxlmfs7j.png" alt="Always Has Been meme: me asking if it's been on the GPU, EEVEE replying that it always has been"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.blender.org/manual/en/latest/render/eevee/index.html" rel="noopener noreferrer"&gt;EEVEE&lt;/a&gt; is a GPU rasterizer, basically a game engine renderer.&lt;/p&gt;

&lt;p&gt;While it rendered, the card sat at about 80% utilisation and 2.2 GB of VRAM.&lt;/p&gt;

&lt;p&gt;There was no idle GPU waiting to be switched on.&lt;/p&gt;

&lt;p&gt;And the other renderer, &lt;a href="https://docs.blender.org/manual/en/latest/render/cycles/index.html" rel="noopener noreferrer"&gt;Cycles&lt;/a&gt;, is a path tracer, which on a GTX 1650 would have been &lt;em&gt;slower&lt;/em&gt;, not faster.&lt;/p&gt;

&lt;p&gt;So the real question was never "CPU or GPU".&lt;/p&gt;

&lt;p&gt;It was "what is each frame paying for?"&lt;/p&gt;

&lt;p&gt;Three settings turned out to dominate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;What it costs&lt;/th&gt;
&lt;th&gt;Draft value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Screen-space ray tracing&lt;/td&gt;
&lt;td&gt;reflections on the wet street, the single biggest per-frame cost&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion blur&lt;/td&gt;
&lt;td&gt;renders extra time steps for every frame&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Samples&lt;/td&gt;
&lt;td&gt;anti-aliasing and soft shadows, linear in render time&lt;/td&gt;
&lt;td&gt;8 instead of 16 to 24&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fswk7oy0xhwfcxeins4fe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fswk7oy0xhwfcxeins4fe.png" alt="Diagram: the GTX 1650 already at about 80% busy with 2.2 of 4 GB VRAM used, the three expensive features each frame pays for being switched off, and seconds per frame dropping from about 0.6 to about 0.33, taking the full film from about 18 minutes to about 10"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the render script grew a &lt;code&gt;--fast&lt;/code&gt; flag that flips exactly those three:&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;fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--fast&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;args&lt;/span&gt;          &lt;span class="c1"&gt;# draft: no ray tracing, no motion blur, 8 samples
&lt;/span&gt;&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eevee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;taa_render_samples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;samples&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eevee&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;use_raytracing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eevee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;use_raytracing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;use_motion_blur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;At 360p that took a frame from about 0.6 s to about 0.33 s.&lt;/p&gt;

&lt;p&gt;Roughly 2x.&lt;/p&gt;

&lt;p&gt;The full 58 second film is 1,760 frames, so a review render went from around 18 minutes to around 10.&lt;/p&gt;

&lt;p&gt;What you give up is real but harmless.&lt;/p&gt;

&lt;p&gt;The street looks flatter without the ray-traced reflections, and fast motion looks crisper without blur.&lt;/p&gt;

&lt;p&gt;Nobody reviewing "does Miles land on the ledge" cares about either.&lt;/p&gt;

&lt;p&gt;The finals still get everything.&lt;/p&gt;

&lt;p&gt;A few other levers came along for the ride:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A 270p option&lt;/strong&gt; (480x270) for the quickest "is the blocking right" passes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contact sheets.&lt;/strong&gt; A strip of stills at 25% resolution and 8 samples, before committing to any video at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial re-renders.&lt;/strong&gt; &lt;code&gt;05_render.py -- full 33 &amp;lt;from_frame&amp;gt;&lt;/code&gt; re-renders only from a given frame onward and reuses the earlier frames, for when only the ending changed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lesson is the same one you learn profiling a slow API.&lt;/p&gt;

&lt;p&gt;Before you ask for a bigger machine, find out what the work actually is.&lt;/p&gt;

&lt;p&gt;Most of the time you are paying for something nobody looks at.&lt;/p&gt;
&lt;h2&gt;
  
  
  "How do I go back? Is there a snapshot button?"
&lt;/h2&gt;

&lt;p&gt;Blender has more undo than you would expect, in layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ctrl+Z and Ctrl+Shift+Z&lt;/strong&gt; for in-session undo and redo, plus &lt;em&gt;Edit → Undo History&lt;/em&gt; to jump back several steps at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File → Revert&lt;/strong&gt; to throw away unsaved changes and reload from disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.blend1&lt;/code&gt; backups.&lt;/strong&gt; Every save keeps the previous version next to the file, and &lt;em&gt;Preferences → Save &amp;amp; Load → Save Versions&lt;/em&gt; sets how many (&lt;a href="https://docs.blender.org/manual/en/latest/editors/preferences/save_load.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File → Recover → Auto Save&lt;/strong&gt; for when Blender crashes, which it will.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All useful, and all beside the point here.&lt;/p&gt;

&lt;p&gt;Because when the &lt;code&gt;.blend&lt;/code&gt; is generated, it is not the source of truth.&lt;/p&gt;

&lt;p&gt;The scripts are.&lt;/p&gt;

&lt;p&gt;So the real snapshot button is git:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; miles/scripts/04_street.py    &lt;span class="c"&gt;# pick a commit&lt;/span&gt;
git stash                                          &lt;span class="c"&gt;# park current work&lt;/span&gt;
git checkout &amp;lt;commit&amp;gt; &lt;span class="nt"&gt;--&lt;/span&gt; miles/                    &lt;span class="c"&gt;# that version's scripts and assets&lt;/span&gt;
blender &lt;span class="nt"&gt;-b&lt;/span&gt; build/character.blend &lt;span class="nt"&gt;--python&lt;/span&gt; scripts/04_street.py   &lt;span class="c"&gt;# rebuild that scene&lt;/span&gt;
git checkout HEAD &lt;span class="nt"&gt;--&lt;/span&gt; miles/ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git stash pop       &lt;span class="c"&gt;# and come back&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If the scene is generated from code, version the code, not the binary.&lt;/p&gt;

&lt;p&gt;I commit after every review round now, so any version I ever showed someone can be rebuilt exactly.&lt;/p&gt;

&lt;p&gt;The old rendered videos stick around too (&lt;code&gt;street_v1&lt;/code&gt;, &lt;code&gt;v4a&lt;/code&gt; and friends), because sometimes you just want to play two versions side by side without rebuilding anything.&lt;/p&gt;
&lt;h2&gt;
  
  
  A reader comment that found a real bug
&lt;/h2&gt;

&lt;p&gt;Part one got &lt;a href="https://dev.to/lewisywliu/comment/3gf6d"&gt;a comment&lt;/a&gt; from &lt;a href="https://dev.to/lewisywliu"&gt;@lewisywliu&lt;/a&gt; that was genuinely better than some docs I have read.&lt;/p&gt;

&lt;p&gt;Two tips, paraphrased:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;BVH files define their own euler rotation order per joint, and the importer does not always match it, so wrists and feet can end up subtly wrong even when the hips look fine. If a pose reads almost right but the hands are off, check euler order before touching offsets.&lt;/p&gt;

&lt;p&gt;Consecutive clips with different hip heights, like land to idle or crouch to stand, need the vertical offset blended too, otherwise the character floats or sinks for a few frames while the horizontal path snaps correctly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first one turned out to be a near miss.&lt;/p&gt;

&lt;p&gt;The second one was a bug I already had and had not seen.&lt;/p&gt;
&lt;h3&gt;
  
  
  Tip 1: euler order, or why the hands go wrong first
&lt;/h3&gt;

&lt;p&gt;A &lt;a href="https://research.cs.wisc.edu/graphics/Courses/cs-838-1999/Jeff/BVH.html" rel="noopener noreferrer"&gt;BVH file&lt;/a&gt;, which is what the &lt;a href="http://mocap.cs.cmu.edu/" rel="noopener noreferrer"&gt;CMU mocap library&lt;/a&gt; ships as, stores each joint's rotation as three angles.&lt;/p&gt;

&lt;p&gt;But it also stores the &lt;em&gt;order&lt;/em&gt; those three rotations are applied in, and that order is per joint.&lt;/p&gt;

&lt;p&gt;One joint can say Z then X then Y, the next can say Y then X then Z.&lt;/p&gt;

&lt;p&gt;That matters because &lt;a href="https://en.wikipedia.org/wiki/Euler_angles" rel="noopener noreferrer"&gt;rotations do not commute&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Rotate 30 degrees around Z and then 45 around X, and you end up facing somewhere different from 45 around X and then 30 around Z.&lt;/p&gt;

&lt;p&gt;Same numbers, different pose.&lt;/p&gt;

&lt;p&gt;Now put that in a skeleton.&lt;/p&gt;

&lt;p&gt;Every bone inherits its parent's rotation, so a small error at the shoulder moves the elbow, which moves the wrist, which moves the hand.&lt;/p&gt;

&lt;p&gt;The error compounds down the chain.&lt;/p&gt;

&lt;p&gt;The hips are at the top of the tree, so they look perfect.&lt;/p&gt;

&lt;p&gt;The hands are at the bottom, so they are the first thing that looks "almost right but weird".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgmljhaqv6wxng70eebcn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgmljhaqv6wxng70eebcn.png" alt="Diagram: the same bone chain from hips to hand drawn twice, once with each joint's own rotation order and once with one fixed order for every joint, identical at the hips and spine and drifting further apart at each joint until the hand is visibly off"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I checked the importer call, half expecting the worst.&lt;/p&gt;

&lt;p&gt;It already passed &lt;code&gt;rotate_mode="NATIVE"&lt;/code&gt; to &lt;a href="https://docs.blender.org/api/current/bpy.ops.import_anim.html" rel="noopener noreferrer"&gt;&lt;code&gt;bpy.ops.import_anim.bvh&lt;/code&gt;&lt;/a&gt;, which means "use each joint's own order from the file".&lt;/p&gt;

&lt;p&gt;So we were safe.&lt;/p&gt;

&lt;p&gt;But it is now the first thing on my list if a CMU clip ever shows odd wrists or ankles, before anyone reaches for an offset hack.&lt;/p&gt;
&lt;h3&gt;
  
  
  Tip 2: hip height across a blend, which bit me
&lt;/h3&gt;

&lt;p&gt;Quick refresher on how the clips are chained.&lt;/p&gt;

&lt;p&gt;Each clip is mostly "in place", so to make Miles actually travel, each clip's movement is added on top of wherever the last one ended.&lt;/p&gt;

&lt;p&gt;A running sum.&lt;/p&gt;

&lt;p&gt;That is &lt;a href="https://docs.unity3d.com/Manual/RootMotion.html" rel="noopener noreferrer"&gt;root motion&lt;/a&gt;, the same idea game engines use.&lt;/p&gt;

&lt;p&gt;Everyone remembers to blend the horizontal part of that path.&lt;/p&gt;

&lt;p&gt;The second tip was that the &lt;em&gt;vertical&lt;/em&gt; part needs blending too.&lt;/p&gt;

&lt;p&gt;(Game engines usually call that Y. Blender is Z-up, because of course it is.)&lt;/p&gt;

&lt;p&gt;On flat ground I never saw a problem.&lt;/p&gt;

&lt;p&gt;On raised surfaces I had exactly this bug, and I only found it once I measured it.&lt;/p&gt;

&lt;p&gt;The probe was simple: on every frame through each blend, take the gap between the lowest toe and the surface under him.&lt;/p&gt;

&lt;p&gt;The target is about +5 cm, because the toe bone sits a little above the sole of the shoe.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Blend, on a raised surface&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Crouch → web shot, on a board's top ledge&lt;/td&gt;
&lt;td&gt;sinks ~10 cm for 3 frames&lt;/td&gt;
&lt;td&gt;+0.03 to +0.07 m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crouch → moonwalk, on a billboard catwalk&lt;/td&gt;
&lt;td&gt;pops up 22 cm, then settles&lt;/td&gt;
&lt;td&gt;+0.02 to +0.10 m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crouch → taunt, on a rooftop&lt;/td&gt;
&lt;td&gt;drifts up 10 cm&lt;/td&gt;
&lt;td&gt;+0.04 to +0.07 m&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A 22 cm pop is a Spider-Man who briefly levitates for no reason.&lt;/p&gt;

&lt;p&gt;Which, to be fair, is on brand, but not what I wanted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first fix did exactly what the tip says, and it did nothing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I eased the root height smoothly across the blend window instead of letting it step.&lt;/p&gt;

&lt;p&gt;The numbers barely moved.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fll4jeo91cy98mi58r7f7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fll4jeo91cy98mi58r7f7.png" alt="Guy hammering nails into sand meme: me, hammering " height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The measurement showed why.&lt;/p&gt;

&lt;p&gt;The hips were not the problem.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;crossfaded pose&lt;/em&gt; was.&lt;/p&gt;

&lt;p&gt;Halfway between a crouch and a standing pose, the blended legs are half extended, so the feet rise on their own, no matter how gently the hips move.&lt;/p&gt;

&lt;p&gt;A smooth curve for the hip height cannot follow what the legs are doing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbp8x6ur73ig39g6v80ky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbp8x6ur73ig39g6v80ky.png" alt="Diagram: three stick figures, crouch, mid-blend and standing, where the mid-blend figure's feet float above the ledge, above a graph of toe height where easing the hips still pops up 22 cm while per-frame contact stays between 2 and 10 cm"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked was to stop predicting and start measuring.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For every frame in a raised shot, find the lowest toe, and move the root so that toe sits a fixed distance above the surface.&lt;/p&gt;

&lt;p&gt;Then smooth it lightly so it does not jitter.&lt;/p&gt;

&lt;p&gt;This is the real function, trimmed a little:&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;contact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toe&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;smooth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Keep the lowest toe `toe` above a surface at height `floor` for every frame.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;frames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;fc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fcurve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;base&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;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&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;fc&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;matrix_world&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bones&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;
                       &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mixamorig:LeftToeBase&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;mixamorig:RightToeBase&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;corr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&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;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;floor&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;toe&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;z&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;z&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# a hop in the clip stays a hop
&lt;/span&gt;    &lt;span class="n"&gt;sm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;corr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;smooth&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;smooth&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="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;corr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;smooth&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;smooth&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
          &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;corr&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;                                   &lt;span class="c1"&gt;# average over ±2 frames
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                                     &lt;span class="c1"&gt;# remove back to front: indices stay valid
&lt;/span&gt;        &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kp&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keyframe_points&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;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;kp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;co&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;end&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;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keyframe_points&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keyframe_points&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sm&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;anim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# a key on EVERY frame
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That &lt;code&gt;limit=0.35&lt;/code&gt; is load bearing.&lt;/p&gt;

&lt;p&gt;Without a cap, one clip that genuinely hops at the end got "corrected" by &lt;strong&gt;8 metres&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The function saw feet in the air, decided the floor must be way off, and dragged the whole character to meet them.&lt;/p&gt;

&lt;p&gt;The ±35 cm clamp means a real hop stays a hop.&lt;/p&gt;

&lt;p&gt;And yes, that "remove back to front" comment is a scar.&lt;/p&gt;

&lt;p&gt;Which brings us to the bugs.&lt;/p&gt;
&lt;h2&gt;
  
  
  Three bugs the measurement dug up
&lt;/h2&gt;

&lt;p&gt;Once there was a probe printing a number per frame, the number started pointing at things I would never have seen in a viewport.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Miles, 18 metres up, for exactly one frame
&lt;/h3&gt;

&lt;p&gt;An earlier floor-lock pass keyed the root height on every &lt;em&gt;other&lt;/em&gt; frame, with linear interpolation in between.&lt;/p&gt;

&lt;p&gt;That is a perfectly reasonable thing to do inside one shot.&lt;/p&gt;

&lt;p&gt;It is a terrible thing to do across a hard cut.&lt;/p&gt;

&lt;p&gt;At a cut from a rooftop to the street, the frame between two keys landed halfway between two shots.&lt;/p&gt;

&lt;p&gt;Linear interpolation averaged "rooftop" and "street" and put Miles about 18 m up, in the sky above the street, for one frame.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbq5e0vnim7x9xe9mjwq2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbq5e0vnim7x9xe9mjwq2.png" alt="Diagram: root height across a rooftop to street cut. With a key every 2nd frame and linear interpolation, frame 1037 lands halfway between the two shots. With a key on every frame, the height steps cleanly at the cut"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At 30 fps that is a 33 millisecond glitch.&lt;/p&gt;

&lt;p&gt;You would never catch it playing the video.&lt;/p&gt;

&lt;p&gt;The probe flagged it instantly.&lt;/p&gt;

&lt;p&gt;The fix: key every frame inside these ranges, so there is no in-between frame left to interpolate.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Hard cuts on fractional frames
&lt;/h3&gt;

&lt;p&gt;Clips are scheduled in float frames, because blend lengths and speeds are computed.&lt;/p&gt;

&lt;p&gt;So a "hard cut" could start at frame 1037.4.&lt;/p&gt;

&lt;p&gt;Blender renders whole frames, which meant frame 1037 showed the old pose at the new position.&lt;/p&gt;

&lt;p&gt;Half of one shot glued to half of another.&lt;/p&gt;

&lt;p&gt;The fix: hard cuts snap to a whole frame with &lt;code&gt;math.ceil&lt;/code&gt;, and a shot's last frame is &lt;code&gt;ceil(end) - 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the animation version of an off-by-one, and it was hiding inside a float.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Deleting keyframes while iterating over them
&lt;/h3&gt;

&lt;p&gt;This one is a classic, and I still walked straight into it.&lt;/p&gt;

&lt;p&gt;The code collected references to the keyframes it wanted to replace, then looped over them calling &lt;code&gt;keyframe_points.remove(kp, fast=True)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But removing a keyframe shifts every index after it.&lt;/p&gt;

&lt;p&gt;So the next reference in the list now pointed at its &lt;em&gt;neighbour&lt;/em&gt;, and the loop silently deleted the wrong key.&lt;/p&gt;

&lt;p&gt;The first frame of two shots ended up with no height key at all.&lt;/p&gt;

&lt;p&gt;It is the same bug as removing items from a Python list inside &lt;code&gt;for x in items:&lt;/code&gt;, just wearing a 3D costume.&lt;/p&gt;

&lt;p&gt;The fix is the one in the snippet above: collect indices, then remove from the back.&lt;/p&gt;

&lt;p&gt;The whole lesson of this section is not about any one of those bugs.&lt;/p&gt;

&lt;p&gt;It is that the first fix looked right in theory and did nothing in practice, and only a number per frame could tell me that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not eyeball root motion. Measure it.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The probe became a test suite
&lt;/h2&gt;

&lt;p&gt;Once the foot probe existed, it seemed silly to only run it when I suspected a problem.&lt;/p&gt;

&lt;p&gt;So it became &lt;code&gt;scripts/check_support.py&lt;/code&gt;, a permanent pre-render check.&lt;/p&gt;

&lt;p&gt;It is about 40 lines and it answers one question, sampling every third frame of the film: is there something under his feet?&lt;br&gt;
&lt;/p&gt;
&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    F[Next frame] --&amp;gt; A{airborne clip or upside down?}
    A --&amp;gt;|yes, swing, hang, flip| F
    A --&amp;gt;|no| T[Find the lowest toe]
    T --&amp;gt; R[Ray cast straight down]
    R --&amp;gt; M{hit Miles, a web, a spider or cash?}
    M --&amp;gt;|yes, step past it| R
    M --&amp;gt;|no| G{gap over 25 cm or sinking?}
    G --&amp;gt;|no| F
    G --&amp;gt;|yes| B[Flag the frame]
    B --&amp;gt; F

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef step fill:#6ea8ff,stroke:#2f62c4,color:#1a1a1a
    classDef bad fill:#ff9a5c,stroke:#c4602a,color:#1a1a1a
    classDef start fill:#e9ecef,stroke:#6c757d,color:#1a1a1a

    class A,M,G decision
    class T,R step
    class B bad
    class F start&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;It uses Blender's &lt;a href="https://docs.blender.org/api/current/bpy.types.Scene.html#bpy.types.Scene.ray_cast" rel="noopener noreferrer"&gt;&lt;code&gt;scene.ray_cast&lt;/code&gt;&lt;/a&gt;, ignores anything that is part of the character or the props (a web is not a floor, sadly), skips the moments where he is supposed to be in the air, and groups flagged frames into runs so the output reads like a test report.&lt;/p&gt;

&lt;p&gt;I run it before every render, the same way you would not deploy without running the tests.&lt;/p&gt;

&lt;p&gt;And it caught things a visual check flat out missed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Moonwalking on thin air.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The opening of this post.&lt;/p&gt;

&lt;p&gt;Miles moonwalked 6.5 m past the edge of a billboard's frame, and, for bonus points, in the opposite direction to the customers walking off the screen behind him.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj6f0jq1e751fxmvlt7kt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj6f0jq1e751fxmvlt7kt.png" alt="Spirit Halloween costume meme: "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fix had two parts.&lt;/p&gt;

&lt;p&gt;A steel catwalk under the screen, because real billboards have one for maintenance anyway.&lt;/p&gt;

&lt;p&gt;And the moonwalk now goes &lt;em&gt;with&lt;/em&gt; the customers, which is a better joke.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A 40 cm stance on a 25 cm board.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;He stood on top of a wall board whose top was 25 cm deep.&lt;/p&gt;

&lt;p&gt;His stance was 40 cm.&lt;/p&gt;

&lt;p&gt;One foot was hanging off the front, standing on air.&lt;/p&gt;

&lt;p&gt;The fix was a 1.25 m service ledge on top of the board.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A turn that swung him 2 m off a ledge.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was my favourite, because the cause was pure geometry.&lt;/p&gt;

&lt;p&gt;In-place clips leave the rig's root pivot nowhere near the hips.&lt;/p&gt;

&lt;p&gt;In this case it was about 4 m away.&lt;/p&gt;

&lt;p&gt;So when a blend rotated the root to turn him 150 degrees, his whole body orbited a point 4 m away, like a door swinging on a hinge in the next room.&lt;/p&gt;

&lt;p&gt;Mid-turn, his hips were 2 m off the ledge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Filciblzm8hoxk64l5tvj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Filciblzm8hoxk64l5tvj.png" alt="Diagram: top-down view of a narrow ledge. Before: rotating the root about a pivot 4 m away swings the hips in an arc about 2 m off the ledge. After: keying the root every frame keeps the hips on a straight line while the body turns in place"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fix was to key the root per frame during a turning blend, so the hips travel in a straight line while the body rotates around them.&lt;/p&gt;

&lt;p&gt;A turn is a rotation about &lt;em&gt;some&lt;/em&gt; point.&lt;/p&gt;

&lt;p&gt;It is worth checking which one.&lt;/p&gt;
&lt;h2&gt;
  
  
  Small workflow wins
&lt;/h2&gt;

&lt;p&gt;None of these deserve a section, but all of them saved real time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Burned-in review copies.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every render also writes a &lt;code&gt;&amp;lt;name&amp;gt;_tc.mp4&lt;/code&gt; with the Blender frame number and "time / total" stamped in the corner.&lt;/p&gt;

&lt;p&gt;When someone says "f1201 looks off", that maps straight to a frame in the scene.&lt;/p&gt;

&lt;p&gt;No more "uh, around the middle, after the bus thing".&lt;/p&gt;



&lt;p&gt;&lt;em&gt;&lt;br&gt;
Your team's attention is limited, and the deluge of AI-generated code is making it harder to keep production secure and reliable without slowing you down.&lt;/em&gt;&lt;/p&gt;
&lt;em&gt;

&lt;p&gt;I'm building &lt;strong&gt;LiveReview&lt;/strong&gt;, a blast-radius aware AI code review built for your business-critical systems.&lt;/p&gt;

&lt;p&gt;Instead of presenting every diff with equal emphasis, &lt;strong&gt;LiveReview scores each change by blast radius — how far its impact reaches through your call graph — so you can focus attention where it actually matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spend code review effort where business risk is highest — not spread evenly across every diff.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;
        LiveReview
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Risk-Aware AI Code Review for Business-Critical Systems
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/png/logo-with-text.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fpng%2Flogo-with-text.png" alt="LiveReview" height="80"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml" rel="noopener noreferrer"&gt;&lt;img alt="mcp-testcases.yml" title="mcp-testcases.yml: MCP integration test suite" src="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml/badge.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LiveReview: Risk-Aware AI Code Review for Business-Critical Systems&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;LiveReview is built for engineering teams that ship often, but also care about keeping production stable for customers. It approves some 40-50% of low-risk changes automatically, and it gives your team the tools to handle the rest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand risky changes faster.&lt;/li&gt;
&lt;li&gt;Fix problems in those changes, with human attention directed to the work that needs it.&lt;/li&gt;
&lt;li&gt;Enforce your engineering standards.&lt;/li&gt;
&lt;li&gt;Learn from production outcomes, such as incidents, rollbacks, and queue wait times.&lt;/li&gt;
&lt;li&gt;Improve the review process over time and make it more efficient.&lt;/li&gt;
&lt;li&gt;Connect to the major engineering tools that your team already uses.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Auto-Approve: Ship the Safe 50% Without a Human&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Auto-Approve Upto 50% of Your Pull Requests, Safely.&lt;/strong&gt; Risk scoring decides which MRs/PRs are safe enough to ship without a human, so your team only spends review time on risky and critical changes.&lt;/p&gt;


  
    
    &lt;span class="m-1"&gt;demo.mp4&lt;/span&gt;
    
  

  

  


&lt;p&gt;&lt;i&gt;LiveReview auto-approving a low-risk change while&lt;/i&gt;…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;b&gt;Click below to try LiveReview with your codebase:&lt;/b&gt;&lt;/p&gt;

&lt;/em&gt;&lt;p&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hexmos.com/livereview" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvls0pq7nymbrll98je6s.png" alt="LiveReview Banner"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blender</category>
      <category>python</category>
      <category>gamedev</category>
      <category>debugging</category>
    </item>
    <item>
      <title>I Made Spider-Man Swing Without Animating a Single Frame</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Sat, 03 Oct 2026 18:05:25 +0000</pubDate>
      <link>https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7</link>
      <guid>https://dev.to/lovestaco/i-made-spider-man-swing-without-animating-a-single-frame-blender-rigging-and-mcp-14f7</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. &lt;a href="https://github.com/HexmosTech/LiveReview/" rel="noopener noreferrer"&gt;Star us&lt;/a&gt; to help devs discover the project, give it a try, and share your feedback to help improve the product.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yes, that is Miles Morales.&lt;/p&gt;

&lt;p&gt;He drops in on a web, lands a superhero crouch, fires a web off screen, and yanks a giant DEV board into frame.&lt;/p&gt;

&lt;p&gt;I did not animate a single frame of that by hand.&lt;/p&gt;

&lt;p&gt;I also had never opened Blender before this week.&lt;/p&gt;

&lt;p&gt;This post is the story of how that happened, and the surprising amount of computer science hiding inside a "simple" 3D character.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Blender, of all things
&lt;/h2&gt;

&lt;p&gt;I was trying to make a video.&lt;/p&gt;

&lt;p&gt;Not a screen recording, an actual video with something moving in it.&lt;/p&gt;

&lt;p&gt;My first stop was &lt;a href="https://github.com/heygen-com/hyperframes" rel="noopener noreferrer"&gt;HyperFrames&lt;/a&gt;, which lets you write a video as HTML and render it out.&lt;/p&gt;

&lt;p&gt;It is genuinely great at motion graphics.&lt;/p&gt;

&lt;p&gt;Text that slides in, logos that pop, charts that draw themselves, all of that is lovely.&lt;/p&gt;

&lt;p&gt;But the moment I wanted a &lt;em&gt;character&lt;/em&gt; to do something, it hit a wall.&lt;/p&gt;

&lt;p&gt;HTML can move rectangles around beautifully.&lt;/p&gt;

&lt;p&gt;It cannot make a guy swing on a web.&lt;/p&gt;

&lt;p&gt;So the next question was obvious, if a little scary: what do actual animators use?&lt;/p&gt;

&lt;p&gt;The answer, for free and open source, is &lt;a href="https://www.blender.org/" rel="noopener noreferrer"&gt;Blender&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And Blender has an MCP server, which meant I would not be learning it alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MCP that drove the whole thing
&lt;/h2&gt;

&lt;p&gt;The thing that made this possible for a total beginner is &lt;a href="https://github.com/ahujasid/mcp-for-blender" rel="noopener noreferrer"&gt;blender-mcp&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It connects Blender to an LLM client like Claude Code, so instead of hunting through 400 menus, you say "load this model and relink its textures" and it happens.&lt;/p&gt;

&lt;p&gt;The architecture is worth understanding, because it is simpler than it sounds.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    ME[Me typing a prompt] --&amp;gt; CC[Claude Code]
    CC --&amp;gt;|MCP over stdio| SRV[blender-mcp server]
    SRV --&amp;gt;|JSON over TCP socket| ADDON[Blender add-on]
    ADDON --&amp;gt;|runs Python| BPY[bpy API]
    BPY --&amp;gt; SCENE[Live Blender scene]
    SCENE --&amp;gt;|viewport screenshot| CC

    classDef human fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
    classDef agent fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a
    classDef bridge fill:#6ea8ff,stroke:#2f62c4,color:#1a1a1a
    classDef blender fill:#ff9a5c,stroke:#c4602a,color:#1a1a1a

    class ME human
    class CC agent
    class SRV,ADDON bridge
    class BPY,SCENE blender&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;There are two halves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An add-on inside Blender&lt;/strong&gt; opens a small socket server and waits for commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An MCP server outside Blender&lt;/strong&gt; speaks the Model Context Protocol to the LLM on one side, and forwards JSON commands down that socket on the other.&lt;/p&gt;

&lt;p&gt;The most powerful tool it exposes is &lt;code&gt;execute_blender_code&lt;/code&gt;, which runs arbitrary Python inside Blender through &lt;code&gt;bpy&lt;/code&gt;, Blender's Python API.&lt;/p&gt;

&lt;p&gt;Every button in Blender is just a thin skin over &lt;code&gt;bpy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So an agent that can write &lt;code&gt;bpy&lt;/code&gt; can, in principle, press every button.&lt;/p&gt;

&lt;p&gt;The loop that makes it work is the screenshot tool coming back the other way.&lt;/p&gt;

&lt;p&gt;The agent changes something, grabs the viewport, &lt;em&gt;looks at it&lt;/em&gt;, and decides what to fix next.&lt;/p&gt;

&lt;p&gt;That is the same edit, run, inspect loop you use on code, just pointed at a 3D scene.&lt;/p&gt;

&lt;p&gt;One honest warning: that tool is arbitrary code execution by design. Treat it like you would treat any local automation with a shell, and do not point it at a machine full of secrets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step one: get a character
&lt;/h2&gt;

&lt;p&gt;I am not a 3D modeler, and I was not about to become one by Friday.&lt;/p&gt;

&lt;p&gt;So I grabbed a ready made Miles Morales model, the Spider-Verse Spider-Man, from &lt;a href="https://sketchfab.com/" rel="noopener noreferrer"&gt;Sketchfab&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa9cguod6ys25tyg6bluw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa9cguod6ys25tyg6bluw.gif" alt="Loading Miles into Blender for the first time" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even "just load it" had lessons in it.&lt;/p&gt;

&lt;p&gt;The file came with &lt;strong&gt;four&lt;/strong&gt; copies of Miles standing in a row: masked and unmasked, posed and T-posed.&lt;/p&gt;

&lt;p&gt;The textures pointed at the original author's Windows folders, like &lt;code&gt;C:\Users\someone\Desktop\...&lt;/code&gt;, so the agent had to relink them to the files in the zip.&lt;/p&gt;

&lt;p&gt;And the zip was missing the normal and specular maps.&lt;/p&gt;

&lt;p&gt;In Blender, a texture node pointing at a missing file renders &lt;strong&gt;hot pink&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It is the 3D equivalent of a 404, and it is very, very visible.&lt;/p&gt;

&lt;p&gt;The fix was to disconnect those slots, at the cost of some surface detail on the suit.&lt;/p&gt;

&lt;p&gt;Then I opened the unmasked Miles and his face looked like a horror movie, covered in white lines and dots.&lt;/p&gt;

&lt;p&gt;Turns out the face was fine.&lt;/p&gt;

&lt;p&gt;That model ships with a &lt;strong&gt;143 bone face rig&lt;/strong&gt; (eyelids, brows, lips, jaw, cheeks, even a tongue), and the armature was set to "draw in front", so every one of those bones was painted on top of his skin.&lt;/p&gt;

&lt;p&gt;One checkbox later, he was handsome again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The concept that unlocks everything: rigging
&lt;/h2&gt;

&lt;p&gt;Here is where I learned the word that the rest of this post hangs on.&lt;/p&gt;

&lt;p&gt;A 3D character is, at its core, a &lt;strong&gt;mesh&lt;/strong&gt;: a big list of vertices (Miles has about 27k faces) glued into triangles.&lt;/p&gt;

&lt;p&gt;A mesh is a statue.&lt;/p&gt;

&lt;p&gt;You cannot make a statue wave.&lt;/p&gt;

&lt;p&gt;To move it, you need three more things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An armature&lt;/strong&gt;, which is a skeleton.&lt;/p&gt;

&lt;p&gt;It is a tree of &lt;strong&gt;bones&lt;/strong&gt;, each one a parent of the next: hips to spine to chest to shoulder to upper arm to forearm to hand to fingers.&lt;/p&gt;

&lt;p&gt;Rotate the shoulder and everything below it in the tree comes along for the ride, exactly like a transform hierarchy in any game engine or a nested &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with a CSS &lt;code&gt;transform&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skin weights&lt;/strong&gt;, which bind the mesh to the skeleton.&lt;/p&gt;

&lt;p&gt;Every vertex stores a small table saying how much each bone influences it.&lt;/p&gt;

&lt;p&gt;A vertex on the forearm might be 100% &lt;code&gt;forearm&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A vertex right at the elbow might be 50% &lt;code&gt;upper_arm&lt;/code&gt; and 50% &lt;code&gt;forearm&lt;/code&gt;, which is what lets the elbow bend smoothly instead of folding like a paper straw.&lt;/p&gt;

&lt;p&gt;The math is called linear blend skinning, and it is just a weighted average of matrices:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final_position(v) = Σ  weight(v, bone) × bone_matrix × rest_position(v)
                   bones
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;A rest pose&lt;/strong&gt;, which is the reference everything is measured from.&lt;/p&gt;

&lt;p&gt;That is why every character you download stands in a &lt;strong&gt;T-pose&lt;/strong&gt;, arms straight out.&lt;/p&gt;

&lt;p&gt;It is not a dance move.&lt;/p&gt;

&lt;p&gt;It is the zero point, the &lt;code&gt;git init&lt;/code&gt; of the body, and every animation is stored as rotations &lt;em&gt;relative&lt;/em&gt; to it.&lt;/p&gt;

&lt;p&gt;Put those together and you have a &lt;strong&gt;rig&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And then the naive plan fell apart, because the model's rig had bones but no &lt;em&gt;controls&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnrn9eh1i6wtloviyls0p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnrn9eh1i6wtloviyls0p.png" alt="Gru's plan meme: download a Spider-Man model, make him dance, he has bones but no rig" width="360" height="230"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Forward vs inverse kinematics
&lt;/h2&gt;

&lt;p&gt;I had seen YouTube videos where an animator grabs a character's hips, drags them down, and the knees bend while the feet stay glued to the floor.&lt;/p&gt;

&lt;p&gt;When I dragged Miles's hips, his whole body floated down through the floor like a ghost.&lt;/p&gt;

&lt;p&gt;The difference is one of the nicest ideas in this whole field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forward kinematics (FK)&lt;/strong&gt; is what a raw skeleton does.&lt;/p&gt;

&lt;p&gt;You set the angle of every joint, and the hand ends up wherever the chain of rotations puts it.&lt;/p&gt;

&lt;p&gt;Easy to compute, miserable to animate. Want the hand on the doorknob? Good luck guessing the shoulder, elbow and wrist angles that land it there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inverse kinematics (IK)&lt;/strong&gt; flips the question.&lt;/p&gt;

&lt;p&gt;You say where the hand (or foot) should be, and a solver works backwards to find joint angles that put it there.&lt;/p&gt;

&lt;p&gt;It is literally an optimisation problem solved every frame, and Blender ships &lt;a href="https://docs.blender.org/manual/en/latest/animation/armatures/posing/bone_constraints/inverse_kinematics/introduction.html" rel="noopener noreferrer"&gt;an IK constraint&lt;/a&gt; that does it for you.&lt;/p&gt;

&lt;p&gt;Pin the feet with IK targets, drag the hips down, and the solver &lt;em&gt;has&lt;/em&gt; to bend the knees because the feet are not allowed to move.&lt;/p&gt;

&lt;p&gt;That is the magic in those YouTube videos.&lt;/p&gt;

&lt;p&gt;It is not magic, it is a constraint solver.&lt;/p&gt;

&lt;p&gt;Building a full IK control rig by hand is a real skill though.&lt;/p&gt;

&lt;p&gt;And I wanted Miles doing backflips this week, not next quarter.&lt;/p&gt;
&lt;h2&gt;
  
  
  Mixamo: rigging as a service
&lt;/h2&gt;

&lt;p&gt;Enter &lt;a href="https://www.mixamo.com" rel="noopener noreferrer"&gt;Mixamo&lt;/a&gt;, Adobe's free auto-rigger and animation library.&lt;/p&gt;

&lt;p&gt;The flow is: export your character as an FBX in T-pose, upload it, and Mixamo builds a skeleton and skin weights for you.&lt;/p&gt;

&lt;p&gt;The only thing it asks of you is to place a few markers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Femmbgwovltrnpu7dria0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Femmbgwovltrnpu7dria0.png" alt="Mixamo's auto-rigger asking for markers on the chin, wrists, elbows, knees and groin" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Chin, wrists, elbows, knees, groin.&lt;/p&gt;

&lt;p&gt;Five landmarks, with symmetry on, so really just three clicks per side.&lt;/p&gt;

&lt;p&gt;From those it infers the whole skeleton, which is a nice reminder of how much structure a human body has. If you know where the joints are, the rest is mostly proportions.&lt;/p&gt;

&lt;p&gt;Placement matters more than you would think.&lt;/p&gt;

&lt;p&gt;My first pass had the wrists sitting on the forearms, which would have made his hands bend in the wrong place every time he shot a web.&lt;/p&gt;

&lt;p&gt;I picked &lt;strong&gt;Standard Skeleton (65)&lt;/strong&gt;, which includes finger bones, because a two handed pull without fingers looks like a man wearing oven mitts.&lt;/p&gt;

&lt;p&gt;And then it just... worked.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79qoafe0w7eqtl3k2siz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F79qoafe0w7eqtl3k2siz.gif" alt="Miles rigged by Mixamo, playing an animation" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The rigged character goes straight into Mixamo's library of motion captured animations.&lt;/p&gt;

&lt;p&gt;These are real performers in mocap suits, recorded and cleaned up, so they have the weight shifts and tiny wobbles that hand keyed animation struggles to fake.&lt;/p&gt;

&lt;p&gt;Jumps, climbs, hangs, throws, dances, falls, the lot.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4366zokae1j60wagh0mg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4366zokae1j60wagh0mg.gif" alt="Miles doing a jump to hang animation" width="760" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy4ipcv8y4wekou85akbx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fy4ipcv8y4wekou85akbx.gif" alt="Miles doing an attack animation" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There was one trade off, and it hurt a little.&lt;/p&gt;

&lt;p&gt;Mixamo builds its &lt;em&gt;own&lt;/em&gt; body skeleton.&lt;/p&gt;

&lt;p&gt;It does not know or care about the beautiful 143 bone face rig the model came with.&lt;/p&gt;
&lt;h2&gt;
  
  
  "With skin" and "without skin"
&lt;/h2&gt;

&lt;p&gt;When you download an animation, Mixamo asks whether you want it &lt;strong&gt;with skin&lt;/strong&gt; or &lt;strong&gt;without skin&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is a good little lesson in separating data from presentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With skin&lt;/strong&gt; gives you the mesh, the skeleton and the motion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without skin&lt;/strong&gt; gives you only the skeleton and the motion, a few hundred kilobytes of bone rotations over time.&lt;/p&gt;

&lt;p&gt;Because every clip targets the same 65 bone skeleton with the same bone names, you only need the skin &lt;em&gt;once&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Everything else is just keyframe data keyed by bone name, and Blender happily applies it to any armature whose bones match.&lt;/p&gt;

&lt;p&gt;So I downloaded the first clip with skin and the rest without.&lt;/p&gt;

&lt;p&gt;And I downloaded a lot of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxi28jpi0m04wfin2s2ou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxi28jpi0m04wfin2s2ou.png" alt="A Downloads folder full of Mixamo FBX files" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Seventy eight files.&lt;/p&gt;

&lt;p&gt;Clicked one at a time, because Mixamo has no multi select.&lt;/p&gt;

&lt;p&gt;Some of them even share a name with a different animation inside, because the site just appends &lt;code&gt;(1)&lt;/code&gt; to whatever you downloaded last.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosrafu77gya2e3ehwbya.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fosrafu77gya2e3ehwbya.png" alt="They're the same picture meme: Hanging Idle.fbx vs Hanging Idle (1).fbx, Mixamo says they're the same picture" width="360" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They are not the same picture.&lt;/p&gt;

&lt;p&gt;The agent ended up scanning every FBX to see what was actually inside before trusting a single filename.&lt;/p&gt;
&lt;h2&gt;
  
  
  From a pile of clips to a performance
&lt;/h2&gt;

&lt;p&gt;Here is the whole pipeline, end to end:&lt;br&gt;
&lt;/p&gt;
&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Sketchfab model] --&amp;gt; B[Clean up in Blender&amp;lt;br/&amp;gt;relink textures, drop pink slots]
    B --&amp;gt; C[Export T-pose FBX]
    C --&amp;gt; D[Mixamo auto-rig&amp;lt;br/&amp;gt;5 markers]
    D --&amp;gt; E[Download clips&amp;lt;br/&amp;gt;1 with skin, rest without]
    E --&amp;gt; F[Import as Blender actions]
    F --&amp;gt; G{Clip looks right&amp;lt;br/&amp;gt;from this angle?}
    G --&amp;gt;|no| E
    G --&amp;gt;|yes| H[Chain clips on NLA&amp;lt;br/&amp;gt;crossfades + root motion]
    H --&amp;gt; I[Webs, board, camera]
    I --&amp;gt; J[360p preview render]
    J --&amp;gt; K{Good enough?}
    K --&amp;gt;|no| H
    K --&amp;gt;|yes| L[Final render]

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef source fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
    classDef rig fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef scene fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a
    classDef out fill:#ff9a5c,stroke:#c4602a,color:#1a1a1a

    class G,K decision
    class A,B,C source
    class D,E,F rig
    class H,I scene
    class J,L out&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Each Mixamo clip lands in Blender as an &lt;strong&gt;action&lt;/strong&gt;, a named bundle of animation curves (an f-curve per bone per rotation channel).&lt;/p&gt;

&lt;p&gt;On its own, an action is a single move.&lt;/p&gt;

&lt;p&gt;A performance is several of them in a row, and that is what Blender's &lt;a href="https://docs.blender.org/manual/en/latest/editors/nla/introduction.html" rel="noopener noreferrer"&gt;Nonlinear Animation editor&lt;/a&gt; is for.&lt;/p&gt;

&lt;p&gt;The NLA is basically a video editor timeline for animation.&lt;/p&gt;

&lt;p&gt;Each action becomes a &lt;strong&gt;strip&lt;/strong&gt;, strips sit on tracks, and overlapping strips can &lt;strong&gt;blend&lt;/strong&gt; into each other.&lt;/p&gt;

&lt;p&gt;That blend is what stops a cut from "hanging" to "landing" from snapping like a broken GIF.&lt;/p&gt;

&lt;p&gt;The agent wrapped all of this into a tiny sequencer, so the shot is described as a list of moves instead of a pile of frame numbers:&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;perf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Performer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;perf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hanging Idle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;perf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hard Landing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;perf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Standing 1H Magic Attack 02&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;perf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pull Heavy Object Stop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;perf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Victory Idle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;face&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;perf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# NLA strips, crossfades, stitched root motion
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;blend=6&lt;/code&gt; is a six frame crossfade into the next clip.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;face=65&lt;/code&gt; turns him 65 degrees so the yank points at the board.&lt;/p&gt;

&lt;p&gt;And the "web shot" is, I promise, a clip literally named &lt;em&gt;Magic Attack&lt;/em&gt;. Mixamo does not have a "thwip".&lt;/p&gt;

&lt;p&gt;The interesting problem hiding in &lt;code&gt;build()&lt;/code&gt; is &lt;strong&gt;root motion&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Some clips move the character through space (a landing drops him, a pull leans him back), and each clip starts from its own origin.&lt;/p&gt;

&lt;p&gt;Play two of them back to back naively and Miles teleports back to the start between moves.&lt;/p&gt;

&lt;p&gt;So the sequencer reads where the hips ended in one clip and offsets the next one to begin there.&lt;/p&gt;

&lt;p&gt;It is the same idea as stitching GPS tracks together: each segment is relative, so you keep a running sum.&lt;/p&gt;

&lt;p&gt;Because all timing derives from that list, swapping one clip re-times the webs, the board and the camera automatically.&lt;/p&gt;

&lt;p&gt;The web strands, the board and the camera are not Mixamo at all, they are plain Blender objects keyframed by script to fire on the frame where his hand reaches out.&lt;/p&gt;
&lt;h2&gt;
  
  
  Iterate at 360p, like a sane person
&lt;/h2&gt;

&lt;p&gt;The first full render was 16 seconds at 720p and took about eight minutes on my GTX 1650.&lt;/p&gt;

&lt;p&gt;That is fine once.&lt;/p&gt;

&lt;p&gt;It is not fine when the feedback is "make it snappier", "make the board bigger" and "his suit is too dark" in the same breath.&lt;/p&gt;

&lt;p&gt;So previews dropped to 360p, roughly 4x faster, and 720p became a finals only thing.&lt;/p&gt;

&lt;p&gt;The same rule you know from code applies: make the feedback loop fast first, then make the output good.&lt;/p&gt;

&lt;p&gt;Two more things that cost real time:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The suit rendered as a flat black blob.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model used a toon shader that collapses lighting into a couple of hard bands, which looks great in a cartoon and terrible when you want to see muscles.&lt;/p&gt;

&lt;p&gt;Swapping in physically based shading, plus a light just for him and red and blue rim lights, brought his shape back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Light grey or dark studio?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A near black suit on a near black background is a silhouette of nothing.&lt;/p&gt;

&lt;p&gt;Lighter backgrounds read better. Dark backgrounds look cooler. The GIF at the top is the dark one, because I have priorities.&lt;/p&gt;
&lt;h2&gt;
  
  
  Escaping the download button
&lt;/h2&gt;

&lt;p&gt;After clicking "Download" 78 times, I asked the obvious engineer question: can I just do this locally?&lt;/p&gt;

&lt;p&gt;Mixamo's rigger and library only live on Adobe's servers.&lt;/p&gt;

&lt;p&gt;But motion capture data is not an Adobe invention.&lt;/p&gt;

&lt;p&gt;The &lt;a href="http://mocap.cs.cmu.edu/" rel="noopener noreferrer"&gt;CMU Graphics Lab Motion Capture Database&lt;/a&gt; has about 2,500 free clips, and there is a &lt;a href="https://github.com/una-dinosauria/cmu-mocap" rel="noopener noreferrer"&gt;BVH mirror on GitHub&lt;/a&gt; with a text index of what each one is.&lt;/p&gt;

&lt;p&gt;Cartwheels, salsa, climbing, martial arts, "pulling a rope", all of it.&lt;/p&gt;

&lt;p&gt;The catch is that CMU's skeleton is not Mixamo's skeleton.&lt;/p&gt;

&lt;p&gt;Moving motion from one skeleton to another is called &lt;strong&gt;retargeting&lt;/strong&gt;, and it is a lovely little linear algebra problem.&lt;/p&gt;

&lt;p&gt;You cannot just copy rotations across, because the two skeletons have different rest poses. A 30 degree rotation from "arms slightly down" is not the same pose as 30 degrees from "arms straight out".&lt;/p&gt;

&lt;p&gt;So the retargeter converts each bone's rotation out of the source rest pose, into a shared world frame, and back into the target's rest pose.&lt;/p&gt;

&lt;p&gt;It also scales the hip travel by leg length, so a tall performer's stride does not make Miles moonwalk.&lt;/p&gt;

&lt;p&gt;The first try rendered... nothing.&lt;/p&gt;

&lt;p&gt;Miles was not in frame. He was not anywhere.&lt;/p&gt;

&lt;p&gt;The BVH rest pose put the hips exactly at the origin, so "rest hip height" was &lt;strong&gt;zero&lt;/strong&gt;, and the scale factor was a division by it.&lt;/p&gt;

&lt;p&gt;Miles had been launched into orbit by a divide by zero.&lt;/p&gt;

&lt;p&gt;Fixed by measuring the floor from the lowest foot in the clip instead, and suddenly he was walking, dancing and cartwheeling from a dataset recorded at Carnegie Mellon years ago.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I actually learned
&lt;/h2&gt;

&lt;p&gt;I set out to make a guy swing on a web.&lt;/p&gt;

&lt;p&gt;I came back with a list of concepts I did not expect from an art tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A character is a &lt;strong&gt;mesh&lt;/strong&gt; deformed by a &lt;strong&gt;tree of bones&lt;/strong&gt;, via per vertex &lt;strong&gt;weights&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Animation is &lt;strong&gt;rotations relative to a rest pose&lt;/strong&gt;, which is why everything T-poses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IK&lt;/strong&gt; is a constraint solver, and it is the difference between a puppet and a corpse.&lt;/li&gt;
&lt;li&gt;Motion clips are &lt;strong&gt;data keyed by bone name&lt;/strong&gt;, so one skin can wear a thousand animations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NLA strips&lt;/strong&gt; are a timeline of reusable actions, and chaining them means stitching &lt;strong&gt;root motion&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retargeting&lt;/strong&gt; is a change of basis, and it will divide by zero if you let it.&lt;/li&gt;
&lt;li&gt;Blender is a Python program with a UI on top, which is why an &lt;strong&gt;MCP bridge&lt;/strong&gt; can drive it at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a ton more in there for a CS person: shader node graphs are dataflow programs, Cycles is a path tracer, physics sims are numerical integrators, geometry nodes are basically a functional language for meshes.&lt;/p&gt;

&lt;p&gt;But one step at a time.&lt;/p&gt;

&lt;p&gt;Next on the list is the face rig I so cruelly abandoned in that pool, and maybe making Miles actually talk.&lt;/p&gt;

&lt;p&gt;If you have played with Blender, rigging or the MCP, tell me what I should pick up next.&lt;/p&gt;



&lt;p&gt;&lt;em&gt;&lt;br&gt;
Your team's attention is limited, and the deluge of AI-generated code is making it harder to keep production secure and reliable without slowing you down.&lt;/em&gt;&lt;/p&gt;
&lt;em&gt;

&lt;p&gt;I'm building &lt;strong&gt;LiveReview&lt;/strong&gt;, a blast-radius aware AI code review built for your business-critical systems.&lt;/p&gt;

&lt;p&gt;Instead of presenting every diff with equal emphasis, &lt;strong&gt;LiveReview scores each change by blast radius — how far its impact reaches through your call graph — so you can focus attention where it actually matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spend code review effort where business risk is highest — not spread evenly across every diff.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;
        LiveReview
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Blast-Radius Aware AI Code Review for Business-Critical Systems
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/png/logo-with-text.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fpng%2Flogo-with-text.png" alt="LiveReview" height="80"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml" rel="noopener noreferrer"&gt;&lt;img alt="mcp-testcases.yml" title="mcp-testcases.yml: MCP integration test suite" src="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml/badge.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LiveReview: Risk-Aware AI Code Review for Business-Critical Systems&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;LiveReview is an AI code reviewer that scores every hunk of a diff by &lt;strong&gt;blast radius&lt;/strong&gt;: how far a change reaches through your call graph, how much persistent state it touches, and how well-tested it is. A 3-line change to a shared auth check can outrank a 300-line UI tweak. Your team's attention goes to the highest-risk code first, not spread evenly across every diff.&lt;/p&gt;


  
    
    &lt;span class="m-1"&gt;blast-radius-demo.mp4&lt;/span&gt;
  

  

  


&lt;p&gt;&lt;i&gt;LiveReview's Blast Radius &amp;amp; Review Priority scoring, live in the diff viewer.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;The exact math, not a black box&lt;/th&gt;

&lt;th&gt;Visualize blast radius at a glance&lt;/th&gt;

&lt;th&gt;Every factor that feeds the score&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-3.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-3.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-4.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-4.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-2.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-2.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;&lt;/p&gt;

How does Blast Radius scoring work? (a more technical explanation)

&lt;p&gt;&lt;strong&gt;Here's the goal:&lt;/strong&gt;&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;A 3-line fix in a function used by 40 other files, that also writes to a database, should score high.&lt;/li&gt;

&lt;li&gt;A 300-line UI change in one file, fully covered by tests…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;b&gt;Click below to try LiveReview with your codebase:&lt;/b&gt;&lt;/p&gt;

&lt;/em&gt;&lt;p&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hexmos.com/livereview" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvls0pq7nymbrll98je6s.png" alt="LiveReview Banner" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blender</category>
      <category>mcp</category>
      <category>ai</category>
      <category>beginners</category>
    </item>
    <item>
      <title>What Happens When Two Databases Both Say Yes</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Thu, 01 Oct 2026 19:22:55 +0000</pubDate>
      <link>https://dev.to/lovestaco/what-happens-when-two-databases-both-say-yes-43n1</link>
      <guid>https://dev.to/lovestaco/what-happens-when-two-databases-both-say-yes-43n1</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. &lt;a href="https://github.com/HexmosTech/LiveReview/" rel="noopener noreferrer"&gt;Star us&lt;/a&gt; to help devs discover the project, give it a try, and share your feedback to help improve the product.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Your database has one primary and two replicas, and for about two years that is the best decision you ever made.&lt;/p&gt;

&lt;p&gt;Reads scale out across the replicas. Writes all go to the one primary. &lt;/p&gt;

&lt;p&gt;There is exactly one copy of the truth, and it lives on a machine you could point at.&lt;/p&gt;

&lt;p&gt;Then sales closes a deal in Sydney.&lt;/p&gt;

&lt;h2&gt;
  
  
  Physics sends you an invoice
&lt;/h2&gt;

&lt;p&gt;Your primary is in Virginia. Your new users are in Sydney.&lt;/p&gt;

&lt;p&gt;Every write from Sydney now has to cross the Pacific, get acknowledged, and come back. &lt;/p&gt;

&lt;p&gt;That is roughly a 200 millisecond round trip, and no amount of clever indexing touches it, because the delay is not your database being slow.&lt;/p&gt;

&lt;p&gt;It is light being inconveniently finite.&lt;/p&gt;

&lt;p&gt;Two hundred milliseconds does not sound fatal until you notice how many writes hide inside one user action.&lt;/p&gt;

&lt;p&gt;A session token. An analytics event. A "last seen" bump. Suddenly a page that feels instant in Boston feels like dialup in Sydney.&lt;/p&gt;

&lt;p&gt;And then there is the other problem, the one that shows up on a Saturday.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcnvusbf49j8qvvx1unk3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcnvusbf49j8qvvx1unk3.png" alt="Diagram: a single primary in Virginia with two read replicas, a Boston user writing in 12ms, a Sydney user paying a 200ms round trip for every write, and the primary crossed out in red with a box reading " width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If us-east-1 has a bad day, nobody writes anything. Not Sydney, not Boston, not the replicas sitting right there with a full copy of the data.&lt;/p&gt;

&lt;p&gt;They have the bytes. They are just not allowed to touch them.&lt;/p&gt;

&lt;p&gt;One leader means one place to be slow and one place to fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  So you give each region its own leader
&lt;/h2&gt;

&lt;p&gt;This is multi-leader replication, and the idea is exactly as blunt as it sounds.&lt;/p&gt;

&lt;p&gt;Put a leader in each region. Let each one accept writes from the users nearest to it. Then have the leaders ship their changes to each other in the background.&lt;/p&gt;

&lt;p&gt;Sydney writes land in Sydney at 8 milliseconds. Virginia writes land in Virginia at 8 milliseconds. &lt;/p&gt;

&lt;p&gt;A few seconds later, each change has made its way to the other side of the world and both leaders hold the same data.&lt;/p&gt;

&lt;p&gt;You just bought two genuinely good things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write latency collapses&lt;/strong&gt; for everyone who is not sitting next to your original region. Writes are now local.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Losing a region is survivable.&lt;/strong&gt; Sydney goes dark, Virginia keeps taking writes, and when Sydney comes back it catches up from the log.&lt;/p&gt;

&lt;p&gt;Your write availability is no longer the uptime of a single building.&lt;/p&gt;

&lt;p&gt;This is the same shape as single-leader replication, just with the "single" removed. Which is a sentence that should make you slightly nervous, and it should.&lt;/p&gt;

&lt;p&gt;Because the thing that made single-leader so pleasant to reason about was never the leader. It was the &lt;em&gt;one&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bill arrives as a row number
&lt;/h2&gt;

&lt;p&gt;Alan is in Sydney. He opens row 1,000,000 and sets &lt;code&gt;x = 7&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At more or less the same moment, John is in Virginia. He opens the same row and sets &lt;code&gt;x = 9&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sydney accepts Alan's write and returns a 200. Virginia accepts John's write and returns a 200. &lt;/p&gt;

&lt;p&gt;Neither leader hesitated, because at the moment each of them decided, the other write did not exist yet as far as they could tell.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8ycdyndplosrm4nn52vf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8ycdyndplosrm4nn52vf.png" alt="Diagram: Alan writing x=7 to the Sydney leader and John writing x=9 to the Virginia leader at the same moment, both committing and returning 200 OK, with replication still in flight between them, over a red box asking " width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the replication streams cross in the middle of the ocean, and both leaders simultaneously learn that they were lied to.&lt;/p&gt;

&lt;p&gt;So what is &lt;code&gt;x&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;There is no answer to that question hiding in the data. The database has already promised both values to two different humans. &lt;/p&gt;

&lt;p&gt;Somebody is about to be wrong and nobody has told them yet.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;the&lt;/strong&gt; problem with multi-leader replication. Not a problem. The problem. Everything else in this post is a response to it.&lt;/p&gt;

&lt;p&gt;And notice what single-leader gave you for free here. &lt;/p&gt;

&lt;p&gt;With one primary, these two writes would have arrived at the same machine, one would have gone second, and it would have overwritten the first &lt;em&gt;while knowing it was overwriting something&lt;/em&gt;. A &lt;code&gt;SELECT&lt;/code&gt; in between would have seen a consistent story.&lt;/p&gt;

&lt;p&gt;That is not a feature anybody shipped. It is a side effect of there being one queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  "At the same time" is the wrong mental model
&lt;/h2&gt;

&lt;p&gt;It is worth being precise here, because this is where people's intuition quietly breaks.&lt;/p&gt;

&lt;p&gt;Alan and John did not have to write at the same instant for this to happen. They could have been forty seconds apart.&lt;/p&gt;

&lt;p&gt;Two writes are &lt;strong&gt;concurrent&lt;/strong&gt; if neither one knew about the other. That is the entire definition. &lt;/p&gt;

&lt;p&gt;It has nothing to do with wall clock time and everything to do with what information had reached whom.&lt;/p&gt;

&lt;p&gt;Leslie Lamport worked this out in 1978 in &lt;a href="https://lamport.azurewebsites.net/pubs/time-clocks.pdf" rel="noopener noreferrer"&gt;Time, Clocks, and the Ordering of Events in a Distributed System&lt;/a&gt;, and it is still the single most useful thing you can read on the subject.&lt;/p&gt;

&lt;p&gt;If Alan's write had reached Virginia before John started typing, John's write would be a normal overwrite. Not a conflict, just an edit.&lt;/p&gt;

&lt;p&gt;Replication lag is what turns an edit into a conflict.&lt;/p&gt;

&lt;p&gt;Which means your conflict rate is not a property of your users. It is a property of how long your replication takes, multiplied by how much your users overlap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The default answer is "last write wins", and it is worse than it sounds
&lt;/h2&gt;

&lt;p&gt;Most systems that offer multi-leader replication ship with a default conflict resolution policy, and that default is almost always &lt;strong&gt;last write wins&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;DynamoDB &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GlobalTables.html" rel="noopener noreferrer"&gt;global tables&lt;/a&gt; do it. Cassandra does it. It is the policy you get when nobody chose a policy.&lt;/p&gt;

&lt;p&gt;Each write carries a timestamp. When two versions of a row collide, the one with the bigger timestamp survives. The other one is discarded.&lt;/p&gt;

&lt;p&gt;Not flagged. Not queued for review. Discarded.&lt;/p&gt;

&lt;p&gt;Here is the part that gets people. &lt;strong&gt;LWW is a guaranteed data loss mechanism&lt;/strong&gt;, and that is not a criticism, that is its job description. &lt;/p&gt;

&lt;p&gt;If two writes conflict and you keep exactly one, you have deliberately thrown away a write that the database told a user it had accepted.&lt;/p&gt;

&lt;p&gt;Sometimes that is completely fine. If the field is "last known GPS position", dropping the older one is correct and you should feel nothing.&lt;/p&gt;

&lt;p&gt;If the field is "account balance", you have just invented a very exciting kind of bug.&lt;/p&gt;

&lt;p&gt;And then there is the detail that turns a tolerable policy into a haunted one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa4w9ipovxana3vy64cil.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa4w9ipovxana3vy64cil.png" alt="Diagram: a real-time axis showing Alan writing x=7 first and John writing x=9 eighty milliseconds later, then the stored timestamps where Sydney's 120ms-fast clock stamps Alan's write at .124 and Virginia stamps John's at .084, so LWW picks x=7 and silently discards the genuinely newer write" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whose clock?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;LWW compares timestamps generated by two different machines, and those machines do not agree on what time it is. &lt;/p&gt;

&lt;p&gt;NTP keeps them roughly in sync, where "roughly" means tens of milliseconds on a good day and considerably worse on a VM whose host is under load or whose clock just stepped.&lt;/p&gt;

&lt;p&gt;So if Sydney's clock has drifted 120 milliseconds ahead, Alan's &lt;em&gt;earlier&lt;/em&gt; write gets stamped with a &lt;em&gt;later&lt;/em&gt; timestamp.&lt;/p&gt;

&lt;p&gt;LWW dutifully keeps it and throws away John's write, which actually happened second.&lt;/p&gt;

&lt;p&gt;The system did exactly what it was configured to do. It also reverted a value, told the user it had succeeded, and left no trace anywhere that it happened.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fffmj1v671cqyzbzcz7wd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fffmj1v671cqyzbzcz7wd.png" alt="What Year Is It meme captioned " width="360" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the reason Google built &lt;a href="https://cloud.google.com/spanner/docs/true-time-external-consistency" rel="noopener noreferrer"&gt;TrueTime&lt;/a&gt; for Spanner, with GPS receivers and atomic clocks in every datacenter, and then had Spanner deliberately &lt;em&gt;wait out&lt;/em&gt; the clock uncertainty interval before committing. &lt;/p&gt;

&lt;p&gt;They did not solve clock skew. They bounded it, paid for the bound in hardware, and then paid for it again in commit latency.&lt;/p&gt;

&lt;p&gt;If you are doing LWW on timestamps from &lt;code&gt;now()&lt;/code&gt; across regions, you are making the same correctness claim Spanner makes, with none of the equipment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmtd3dwvb81f1cwxprshg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmtd3dwvb81f1cwxprshg.png" alt="Guy Hammering Nails Into Sand meme, the hammer labelled " width="360" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can do instead of picking a loser
&lt;/h2&gt;

&lt;p&gt;The good news is that LWW is a default, not a law. &lt;/p&gt;

&lt;p&gt;Once you accept that conflicts are going to happen, you get to choose what happens next, and there are better options than a coin flip weighted by NTP drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep both and let the application decide.&lt;/strong&gt; CouchDB does this properly: a conflicting document keeps every version as a &lt;a href="https://docs.couchdb.org/en/stable/replication/conflicts.html" rel="noopener noreferrer"&gt;sibling&lt;/a&gt;, picks one deterministically so reads do not break, and leaves the rest sitting there for you to resolve. &lt;/p&gt;

&lt;p&gt;Riak called these siblings too. It is the most honest option, and it is the one that forces you to actually write resolution logic, which is why people avoid it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write a merge function.&lt;/strong&gt; For a lot of real data, "pick a winner" is just the wrong operation. Two users adding different items to a shared cart is a union, not a race. &lt;/p&gt;

&lt;p&gt;Two users editing different fields of the same profile row is two independent edits that a column-level merge handles without anyone losing anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change the data type so conflicts cannot exist.&lt;/strong&gt; This is the good one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replicate the operation, not the value
&lt;/h2&gt;

&lt;p&gt;Here is the move that makes a whole class of conflicts evaporate.&lt;/p&gt;

&lt;p&gt;A conflict happens because two leaders made competing claims about &lt;em&gt;what a value is&lt;/em&gt;. Sydney said the cart has 4 items.&lt;/p&gt;

&lt;p&gt;Virginia said the cart has 4 items. They both read 3 and both added 1.&lt;/p&gt;

&lt;p&gt;Last write wins picks one of those two 4s, and the answer is 4. The customer added two things and the cart grew by one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flazsz4d1999a9pqkcgbc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flazsz4d1999a9pqkcgbc.png" alt="Diagram: on the left, both leaders reading 3 and setting 4, converging on cart = 4 with one item silently lost; on the right, Sydney recording syd += 1 and Virginia recording vir += 1, summing to cart = 5" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now watch what happens if the leaders stop shipping the &lt;em&gt;result&lt;/em&gt; and start shipping the &lt;em&gt;operation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Sydney does not say "the cart is 4". It says "Sydney has contributed 1". Virginia says "Virginia has contributed 1". &lt;/p&gt;

&lt;p&gt;The value of the cart is the sum of everyone's contributions, and both leaders compute 5 without ever disagreeing about anything.&lt;/p&gt;

&lt;p&gt;Nothing had to win, because the two writes were never competing claims in the first place.&lt;/p&gt;

&lt;p&gt;That is a &lt;a href="https://crdt.tech/" rel="noopener noreferrer"&gt;CRDT&lt;/a&gt;, and the counter version of it is so simple you can build one in plain SQL with no special database at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- one row per (cart, replica). each leader only ever touches its own row,&lt;/span&gt;
&lt;span class="c1"&gt;-- so two leaders can never write the same row and never conflict.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;cart_counter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;cart_id&lt;/span&gt;   &lt;span class="nb"&gt;bigint&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;replica&lt;/span&gt;   &lt;span class="nb"&gt;text&lt;/span&gt;    &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- 'syd' | 'iad' | 'fra'&lt;/span&gt;
  &lt;span class="n"&gt;delta&lt;/span&gt;     &lt;span class="nb"&gt;bigint&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&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;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replica&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Sydney's leader, adding an item. it writes replica = 'syd', always.&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;cart_counter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replica&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1841&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'syd'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replica&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cart_counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- the actual value, which every region computes identically&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;cart_counter&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;cart_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1841&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The write set of each leader is disjoint by construction. Replication can arrive in any order, duplicated, delayed by a week, and every region still converges on the same number.&lt;/p&gt;

&lt;p&gt;This is the same principle behind &lt;a href="https://automerge.org/" rel="noopener noreferrer"&gt;Automerge&lt;/a&gt; and &lt;a href="https://yjs.dev/" rel="noopener noreferrer"&gt;Yjs&lt;/a&gt;, which is how collaborative editors let twelve people type into one paragraph without a central referee. &lt;/p&gt;

&lt;p&gt;And it is worth noticing that a Google Doc or a Figma file &lt;em&gt;is&lt;/em&gt; a multi-leader system. Every laptop is a leader. &lt;/p&gt;

&lt;p&gt;The offline mode you shipped last quarter made your mobile app one too.&lt;/p&gt;

&lt;p&gt;The catch is that CRDTs are not free. They carry per-replica metadata, which grows, and they are only convenient for data types somebody has already worked out the merge semantics for: counters, sets, registers, sequences, maps. &lt;/p&gt;

&lt;p&gt;The moment your business rule is "the total must never exceed the credit limit", you are back to needing coordination, because that is a global invariant and CRDTs specifically do not give you those.&lt;/p&gt;
&lt;h2&gt;
  
  
  The conflicts nobody warns you about
&lt;/h2&gt;

&lt;p&gt;So far this has all been about one row, one column. The conflicts that actually ruin a migration weekend are the structural ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto-increment primary keys.&lt;/strong&gt; Two leaders, both confidently handing out id 4817 to different rows. The fix is for each leader to generate ids from a disjoint space: offset sequences, UUIDs, or a &lt;a href="https://en.wikipedia.org/wiki/Snowflake_ID" rel="noopener noreferrer"&gt;Snowflake-style id&lt;/a&gt; with the region baked into the bits. If you are turning on a second leader and have not solved this, solve it first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Unique constraints.&lt;/strong&gt; Two users in two regions both register &lt;code&gt;alan@example.com&lt;/code&gt;. Each leader checks its local table, sees nothing, and allows it. &lt;/p&gt;

&lt;p&gt;The constraint held locally and is violated globally, which is a fun error to discover during replication rather than during signup. Uniqueness is a global invariant and asynchronous replication fundamentally cannot enforce one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Foreign keys and deletes.&lt;/strong&gt; Sydney deletes a parent row. Virginia inserts a child pointing at it. Both are valid locally. &lt;/p&gt;

&lt;p&gt;Together they produce an orphan, and now your replication stream is stuck trying to apply something your schema refuses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Triggers and defaults that call &lt;code&gt;now()&lt;/code&gt;.&lt;/strong&gt; Any logic that is not a pure function of the row will produce different results on different leaders. Replicate the computed value, not the computation.&lt;/p&gt;

&lt;p&gt;Notice the pattern. Everything on that list is a constraint that spans rows your leaders do not coordinate on. &lt;/p&gt;

&lt;p&gt;Single-leader hid all of them behind one serialisation point, for free, and you never knew they were being handled.&lt;/p&gt;
&lt;h2&gt;
  
  
  The strategy that actually ships
&lt;/h2&gt;

&lt;p&gt;Here is what teams running this in production actually do, and it is less glamorous than any of the above.&lt;/p&gt;

&lt;p&gt;They make the conflict impossible.&lt;/p&gt;

&lt;p&gt;Give every record exactly one &lt;strong&gt;home leader&lt;/strong&gt; that owns its writes. All writes for a given customer, tenant, or account are routed to that customer's home region, no matter where the request physically landed.&lt;/p&gt;

&lt;p&gt;Every leader still replicates everything to everywhere, so reads stay local and failover stays possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F10rmvl3tfjh33173664r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F10rmvl3tfjh33173664r.png" alt="Diagram: a router reading home_region from the account row and sending APAC account writes to the Sydney leader and US account writes to the Virginia leader, each owning a disjoint slice of accounts, with async replication flowing both ways, and a yellow box noting " width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the two leaders have &lt;strong&gt;disjoint write sets&lt;/strong&gt;. Sydney writes rows Virginia never writes. There is no conflict to resolve because the two leaders are never making claims about the same data.&lt;/p&gt;

&lt;p&gt;Conflict resolution logic you never run is the only kind that never has a bug.&lt;/p&gt;

&lt;p&gt;This is why multi-leader works beautifully when your data has a natural ownership boundary. Per-customer SaaS. Per-tenant workspaces. Per-region marketplaces. &lt;/p&gt;

&lt;p&gt;It works much less beautifully for a global leaderboard or a shared inventory count, where every write touches data everybody else touches too.&lt;/p&gt;

&lt;p&gt;The routing itself is usually unexciting, which is the point:&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;route_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request_region&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;home&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;home_region&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# cached, changes rarely
&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;home&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;request_region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;local_leader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                 &lt;span class="c1"&gt;# the fast path, ~8ms
&lt;/span&gt;
    &lt;span class="c1"&gt;# cross-region write: slower, but still exactly one leader owns this row,
&lt;/span&gt;    &lt;span class="c1"&gt;# so it is an ordinary overwrite rather than a conflict waiting to converge
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;leader_in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note what that &lt;code&gt;else&lt;/code&gt; branch is buying you. A European user acting on a US-homed account pays the 100 millisecond hop.&lt;/p&gt;

&lt;p&gt;You have traded &lt;em&gt;some&lt;/em&gt; users' write latency for never having to explain to your CFO why a balance went backwards.&lt;/p&gt;

&lt;p&gt;That is usually the right trade, and it is a trade, not a free lunch.&lt;br&gt;
&lt;/p&gt;
&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Write arrives in Frankfurt] --&amp;gt; B{Which region&amp;lt;br/&amp;gt;owns this account?}
    B --&amp;gt;|Frankfurt| C[Local leader, 8 ms]
    B --&amp;gt;|Virginia| D[Forward to Virginia, 100 ms]
    C --&amp;gt; E[Commit, ack the user]
    D --&amp;gt; E
    E --&amp;gt; F[Async replication to every other leader]
    F --&amp;gt; G[All regions can serve reads&amp;lt;br/&amp;gt;and take over on failover]
    B --&amp;gt;|Home region is down| H{Can you wait&amp;lt;br/&amp;gt;for it to come back?}
    H --&amp;gt;|Yes| I[Reject the write, stay conflict-free]
    H --&amp;gt;|No| J[Reassign the home, accept&amp;lt;br/&amp;gt;that you may now conflict]

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef start fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
    classDef good fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef warn fill:#ff9a5c,stroke:#c25f20,color:#1a1a1a

    class A start
    class B,H decision
    class C,D,E,F,G good
    class I,J warn&lt;/code&gt;&lt;/pre&gt;


&lt;h2&gt;
  
  
  The honest caveat: homes move
&lt;/h2&gt;

&lt;p&gt;Ownership routing is clean right up until the moment a region fails, which is the exact scenario you built multi-leader for in the first place.&lt;/p&gt;

&lt;p&gt;Sydney goes down. Its accounts have no home. &lt;/p&gt;

&lt;p&gt;You either reject their writes, which defeats the point, or you reassign their home to Virginia, which means accepting writes for rows Sydney might also have accepted writes for in the seconds before it died but not yet replicated.&lt;/p&gt;

&lt;p&gt;That window is where your conflicts live. Not during normal operation, during failover.&lt;/p&gt;

&lt;p&gt;So the real design is: disjoint write sets almost always, plus a conflict resolution policy you have genuinely thought about for the minutes per year when they are not disjoint. &lt;/p&gt;

&lt;p&gt;Teams that skip the second half discover they needed it during an incident, which is the worst possible time to be reading about version vectors.&lt;/p&gt;

&lt;p&gt;Two smaller things that bite, worth knowing before they bite you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading your own writes gets harder.&lt;/strong&gt; A user writes to their home leader in Virginia, then their next request hits a read replica in Frankfurt that is 400 milliseconds behind. &lt;/p&gt;

&lt;p&gt;Their change is gone. You fix this by pinning a user's session to one region for a while after a write, or by having the client carry the version it last saw and the read wait for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replication topology matters.&lt;/strong&gt; All-to-all is the usual choice and is the most robust, but it lets messages overtake each other, so an update can arrive before the insert it depends on. &lt;/p&gt;

&lt;p&gt;Circular and star topologies are cheaper on connections and much worse on failure, because one dead node breaks the chain. MySQL's circular replication is famous for exactly this.&lt;/p&gt;
&lt;h2&gt;
  
  
  So should you turn this on?
&lt;/h2&gt;

&lt;p&gt;The uncomfortable truth is that most teams reaching for multi-leader replication do not need it.&lt;/p&gt;

&lt;p&gt;If your problem is read latency, you want read replicas, and you already have them. Multi-leader only pays off when &lt;strong&gt;writes&lt;/strong&gt; are the thing crossing an ocean.&lt;/p&gt;

&lt;p&gt;If your problem is regional failover, a single leader with a well-rehearsed promotion runbook gets you most of the way there, with a fraction of the semantics to reason about.&lt;br&gt;
&lt;/p&gt;
&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Users in multiple regions] --&amp;gt; B{Are WRITES the&amp;lt;br/&amp;gt;slow part?}
    B --&amp;gt;|No, reads are| C[Read replicas per region&amp;lt;br/&amp;gt;Keep the single leader]
    B --&amp;gt;|Yes| D{Does the data split&amp;lt;br/&amp;gt;cleanly by owner?}
    D --&amp;gt;|Yes, per tenant or account| E[Multi-leader with home regions&amp;lt;br/&amp;gt;Disjoint write sets]
    D --&amp;gt;|No, everyone writes everything| F{Can you model it&amp;lt;br/&amp;gt;as a CRDT?}
    F --&amp;gt;|Yes, counters sets text| G[CRDTs, converge without picking]
    F --&amp;gt;|No, you need real invariants| H[Distributed SQL&amp;lt;br/&amp;gt;Spanner, CockroachDB, YugabyteDB]
    E --&amp;gt; I[Still need a policy for&amp;lt;br/&amp;gt;the failover window]

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef start fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
    classDef good fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef alt fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a
    classDef warn fill:#ff9a5c,stroke:#c25f20,color:#1a1a1a

    class A start
    class B,D,F decision
    class C,E good
    class G,H alt
    class I warn&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;And if you genuinely need strong consistency across regions, that is what &lt;a href="https://www.cockroachlabs.com/docs/stable/architecture/replication-layer" rel="noopener noreferrer"&gt;CockroachDB&lt;/a&gt; and Spanner are for.&lt;/p&gt;

&lt;p&gt;They run consensus under the hood and charge you for it in write latency, openly, instead of handing you an asynchronous system that looks consistent until it is not.&lt;/p&gt;

&lt;p&gt;That is the actual choice. Not "multi-leader or single-leader", but &lt;em&gt;where you would like to pay&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Single-leader charges distant users on every write. Distributed SQL charges everyone a smaller amount on every write. &lt;/p&gt;

&lt;p&gt;Multi-leader charges nobody anything on the happy path and sends you one enormous invoice on the day two regions disagree about a row.&lt;/p&gt;

&lt;p&gt;Pick the bill you would rather receive at 3am.&lt;/p&gt;
&lt;h2&gt;
  
  
  The one-line version
&lt;/h2&gt;

&lt;p&gt;Multi-leader replication does not create conflicts. &lt;/p&gt;

&lt;p&gt;It reveals that your data model always had them, and that a single primary was quietly doing your reconciliation for free.&lt;/p&gt;

&lt;p&gt;The moment you add a second leader, that work moves to you. &lt;/p&gt;

&lt;p&gt;You can do it with a timestamp comparison and hope, or with disjoint write sets and intent.&lt;/p&gt;

&lt;p&gt;Only one of those lets you sleep.&lt;/p&gt;



&lt;p&gt;&lt;em&gt;&lt;br&gt;
Your team's attention is limited, and the deluge of AI-generated code is making it harder to keep production secure and reliable without slowing you down.&lt;/em&gt;&lt;/p&gt;
&lt;em&gt;

&lt;p&gt;I'm building &lt;strong&gt;LiveReview&lt;/strong&gt;, a blast-radius aware AI code review built for your business-critical systems.&lt;/p&gt;

&lt;p&gt;Instead of presenting every diff with equal emphasis, &lt;strong&gt;LiveReview scores each change by blast radius — how far its impact reaches through your call graph — so you can focus attention where it actually matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spend code review effort where business risk is highest — not spread evenly across every diff.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;
        LiveReview
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Blast-Radius Aware AI Code Review for Business-Critical Systems
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/png/logo-with-text.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fpng%2Flogo-with-text.png" alt="LiveReview" height="80"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml" rel="noopener noreferrer"&gt;&lt;img alt="mcp-testcases.yml" title="mcp-testcases.yml: MCP integration test suite" src="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml/badge.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LiveReview: Risk-Aware AI Code Review for Business-Critical Systems&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;LiveReview is an AI code reviewer that scores every hunk of a diff by &lt;strong&gt;blast radius&lt;/strong&gt;: how far a change reaches through your call graph, how much persistent state it touches, and how well-tested it is. A 3-line change to a shared auth check can outrank a 300-line UI tweak. Your team's attention goes to the highest-risk code first, not spread evenly across every diff.&lt;/p&gt;


  
    
    &lt;span class="m-1"&gt;blast-radius-demo.mp4&lt;/span&gt;
  

  

  


&lt;p&gt;&lt;i&gt;LiveReview's Blast Radius &amp;amp; Review Priority scoring, live in the diff viewer.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;The exact math, not a black box&lt;/th&gt;

&lt;th&gt;Visualize blast radius at a glance&lt;/th&gt;

&lt;th&gt;Every factor that feeds the score&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-3.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-3.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-4.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-4.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-2.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-2.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;&lt;/p&gt;

How does Blast Radius scoring work? (a more technical explanation)

&lt;p&gt;&lt;strong&gt;Here's the goal:&lt;/strong&gt;&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;A 3-line fix in a function used by 40 other files, that also writes to a database, should score high.&lt;/li&gt;

&lt;li&gt;A 300-line UI change in one file, fully covered by tests…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;b&gt;Click below to try LiveReview with your codebase:&lt;/b&gt;&lt;/p&gt;

&lt;/em&gt;&lt;p&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hexmos.com/livereview" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvls0pq7nymbrll98je6s.png" alt="LiveReview Banner" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>database</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>Building a Custom ERP Module on the MERN Stack: Data Modeling, Transactions and Audit Trails</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Tue, 29 Sep 2026 21:25:00 +0000</pubDate>
      <link>https://dev.to/lovestaco/building-a-custom-erp-module-on-the-mern-stack-data-modeling-transactions-and-audit-trails-4516</link>
      <guid>https://dev.to/lovestaco/building-a-custom-erp-module-on-the-mern-stack-data-modeling-transactions-and-audit-trails-4516</guid>
      <description>&lt;p&gt;ERP software handles money, stock, and approvals, so small bugs get expensive fast. A stock count that drifts by a few units or a rounding error in an invoice can take days to trace. &lt;/p&gt;

&lt;p&gt;Off-the-shelf ERPs cover the general case well, but many companies eventually need a custom module for a workflow the big platforms handle awkwardly.&lt;/p&gt;

&lt;p&gt;The MERN stack works well for this. You get flexible schemas for evolving business rules, one language across the whole stack, and real multi-document transactions in MongoDB.&lt;/p&gt;

&lt;p&gt;This guide walks through a small purchasing and inventory module and focuses on 3 things: data modeling, consistent writes, and audit trails.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Example Module
&lt;/h2&gt;

&lt;p&gt;The module has suppliers, products, purchase orders (POs) with line items, and stock levels per warehouse. &lt;/p&gt;

&lt;p&gt;The critical operation is receiving goods: the system must update PO line quantities, change the PO status, and increase stock, all at once. If only half of that happens, the numbers stop matching reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Modeling: Embed What Belongs Together
&lt;/h2&gt;

&lt;p&gt;A simple rule works well for ERP data. Embed things that live and die with the parent, like PO line items. &lt;/p&gt;

&lt;p&gt;Reference things with their own lifecycle, like suppliers and products.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mongoose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lineItemSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;            &lt;span class="c1"&gt;// snapshot at order time&lt;/span&gt;
  &lt;span class="na"&gt;unitPriceCents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;quantityOrdered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;quantityReceived&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;purchaseOrderSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Supplier&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;warehouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Warehouse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;approved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;partially_received&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;received&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cancelled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;draft&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lineItemSchema&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timestamps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;optimisticConcurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;purchaseOrderSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;supplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stockLevelSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;warehouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Warehouse&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;onHand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;stockLevelSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;warehouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few choices here are worth copying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Snapshot fixed values.&lt;/strong&gt; SKU and price are copied into each line, so old orders keep what was actually agreed even if the product changes later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store money as integers.&lt;/strong&gt; JavaScript floats can't represent 0.1 exactly. Cents avoid a whole category of rounding bugs. If you need fractional cents, use &lt;code&gt;Decimal128&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn on optimistic concurrency.&lt;/strong&gt; If 2 people edit the same PO at once, the second save fails instead of silently overwriting the first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a unique compound index on stock.&lt;/strong&gt; One row per product per warehouse, guaranteed by the database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also pays to design indexes around the questions people will actually ask. &lt;/p&gt;

&lt;p&gt;Procurement teams usually want "open orders from supplier X, newest first", and the compound index on &lt;code&gt;supplier&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, and &lt;code&gt;createdAt&lt;/code&gt; answers that without a collection scan. &lt;/p&gt;

&lt;p&gt;Look at the reports and list screens in your mockups before writing schemas, and add indexes for them from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transactions: Keep Related Writes Consistent
&lt;/h2&gt;

&lt;p&gt;Receiving goods touches 2 collections. Without a transaction, a crash between the 2 writes leaves you with phantom inventory. &lt;/p&gt;

&lt;p&gt;MongoDB transactions need a replica set; locally, a single-node one (&lt;code&gt;mongod --replSet rs0&lt;/code&gt;) is enough. &lt;/p&gt;

&lt;p&gt;The easiest API is &lt;code&gt;session.withTransaction()&lt;/code&gt;, which commits for you and retries on transient errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;receiveGoods&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;poId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;receipts&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mongoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;po&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;PurchaseOrder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;poId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;po&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;approved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;partially_received&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;po&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Order cannot receive goods&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;lineId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;receipts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;po&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lineId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantityReceived&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantityOrdered&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Over-receipt on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantityReceived&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;StockLevel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;warehouse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;po&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;warehouse&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;$inc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onHand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;po&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantityReceived&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantityOrdered&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;po&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;received&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;partially_received&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;po&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3 rules keep this safe.&lt;/p&gt;

&lt;p&gt;1) Pass the session to every read and write.&lt;br&gt;
2) Keep side effects like emails or payment calls out of the callback, since &lt;code&gt;withTransaction&lt;/code&gt; may run it more than once.&lt;br&gt;
3) Keep transactions short to avoid lock contention.&lt;/p&gt;

&lt;p&gt;For side effects that must happen, the outbox pattern works nicely. Inside the transaction, insert a record like &lt;code&gt;{ type: 'po.received', poId }&lt;/code&gt; into an &lt;code&gt;outbox&lt;/code&gt; collection. &lt;/p&gt;

&lt;p&gt;A background worker picks these up after the commit and sends the email or notifies the accounting system.&lt;/p&gt;

&lt;p&gt;If the transaction rolls back, the outbox record disappears too, so nothing gets sent for a change that never happened.&lt;/p&gt;

&lt;p&gt;Retry semantics and write conflicts rarely show up in tutorials, which is why companies building finance-adjacent modules often look for &lt;a href="https://incora.software/hire/mern-stack-developers" rel="noopener noreferrer"&gt;MERN stack developers&lt;/a&gt; who have already debugged these issues under real load. &lt;/p&gt;

&lt;p&gt;Warehouse networks are also flaky, so give each receipt an idempotency key, store it under a unique index inside the same transaction, and duplicate requests will fail harmlessly.&lt;/p&gt;
&lt;h2&gt;
  
  
  Audit Trails: Who Changed What, and When
&lt;/h2&gt;

&lt;p&gt;Auditors will ask "who approved this order?" and "why did the price change?". A Mongoose plugin combined with &lt;code&gt;AsyncLocalStorage&lt;/code&gt; answers these automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AsyncLocalStorage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:async_hooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lodash/get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auditContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AsyncLocalStorage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Express middleware, placed after authentication&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withAuditContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;auditContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;auditPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;entity&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;init&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;depopulate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;auditContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;changes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;directModifiedPaths&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;from&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&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="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]));&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AuditLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt;
      &lt;span class="nx"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;entityId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isNew&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;purchaseOrderSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auditPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;entity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PurchaseOrder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key detail is &lt;code&gt;this.$session()&lt;/code&gt;. The audit entry joins the same transaction as the change it describes, so a rollback removes both.&lt;/p&gt;

&lt;p&gt;Small details like this are easy to miss, and a review from &lt;a href="https://incora.software/hire/mern-stack-developers" rel="noopener noreferrer"&gt;experienced MERN developers&lt;/a&gt; at this stage usually costs far less than cleaning up inconsistent audit data after a compliance check.&lt;/p&gt;

&lt;p&gt;2 more things to handle. Query-level updates like the &lt;code&gt;$inc&lt;/code&gt; on stock skip save hooks, so for inventory use a &lt;code&gt;StockMovement&lt;/code&gt; ledger with one row per movement and treat &lt;code&gt;onHand&lt;/code&gt; as a cached total. &lt;/p&gt;

&lt;p&gt;The ledger also lets you rebuild stock for any date, which accountants love at month-end. And make the log append-only by giving the app's database user only &lt;code&gt;insert&lt;/code&gt; and &lt;code&gt;find&lt;/code&gt; rights on the audit collection.&lt;/p&gt;

&lt;p&gt;On the React side, a simple timeline under each record showing who changed which field and when goes a long way. &lt;/p&gt;

&lt;p&gt;Once users can see the history themselves, many "who changed this?" support tickets simply stop coming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Hard Parts
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;mongodb-memory-server&lt;/code&gt; includes &lt;code&gt;MongoMemoryReplSet&lt;/code&gt;, so transactions work in CI without a real cluster. Focus your integration tests on the failure paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 concurrent receipts on the same PO, where one should fail on the version check.&lt;/li&gt;
&lt;li&gt;A thrown error halfway through &lt;code&gt;receiveGoods&lt;/code&gt;, after which stock, PO and audit rows should all be unchanged.&lt;/li&gt;
&lt;li&gt;The same request sent twice with one idempotency key, which should change stock only once.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Embed data that belongs to its parent, store money as integers, and index for the reports people actually use. &lt;/p&gt;

&lt;p&gt;Wrap related writes in &lt;code&gt;withTransaction&lt;/code&gt;, move side effects to an outbox and write audit entries inside the same transaction. &lt;/p&gt;

&lt;p&gt;Get these right, and the rest of the module becomes ordinary CRUD work, which is exactly how ERP code should feel.&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Two-Phase Commit Is a Trap: How Real Systems Do Distributed Transactions</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Mon, 28 Sep 2026 21:55:38 +0000</pubDate>
      <link>https://dev.to/lovestaco/two-phase-commit-is-a-trap-how-real-systems-do-distributed-transactions-9i9</link>
      <guid>https://dev.to/lovestaco/two-phase-commit-is-a-trap-how-real-systems-do-distributed-transactions-9i9</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. &lt;a href="https://github.com/HexmosTech/LiveReview/" rel="noopener noreferrer"&gt;Star us&lt;/a&gt; to help devs discover the project, give it a try, and share your feedback to help improve the product.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you are first building an application, a transaction is the cheapest guarantee you will ever get.&lt;/p&gt;

&lt;p&gt;A customer places an order. You charge their card, reserve the inventory, write a ledger entry for accounting.&lt;/p&gt;

&lt;p&gt;You wrap the whole thing in &lt;code&gt;BEGIN&lt;/code&gt; and &lt;code&gt;COMMIT&lt;/code&gt;, and if any one of those writes falls over, the database quietly rewinds the other two.&lt;/p&gt;

&lt;p&gt;It is as if the order never happened. You probably wrote that code in four minutes and never thought about it again.&lt;/p&gt;

&lt;p&gt;What you were leaning on there are two of the ACID guarantees, doing unpaid labour on your behalf.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atomicity.&lt;/strong&gt; All three writes land, or none of them do. There is no universe where the card gets charged but the stock never gets reserved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isolation.&lt;/strong&gt; While the transaction is in flight, nobody else can see the half-finished middle. Another request checking the customer's balance will not catch a charge for an order that has not finished processing.&lt;/p&gt;

&lt;p&gt;You wrote the SQL. The database did the genuinely hard part, for free, and never asked for credit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then you split the database, and the guarantee quietly leaves
&lt;/h2&gt;

&lt;p&gt;Traffic grows. Writes grow. That one box starts breathing heavily.&lt;/p&gt;

&lt;p&gt;So you do what everyone does. You shard, or you break the monolith into services and give each one its own database.&lt;/p&gt;

&lt;p&gt;The details vary. The outcome is identical: your data now lives on several independent machines that have never been introduced to each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc0oyzabzlf6tky6mq9f7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc0oyzabzlf6tky6mq9f7.png" alt="Diagram: on the left, one database wrapping charge, reserve and ledger in a single BEGIN and COMMIT; on the right, the same three steps landing in three separate databases on three separate machines, with the payment committed, the inventory write rejected, and the ledger never reached" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That one payment flow is now three separate operations against three separate databases.&lt;/p&gt;

&lt;p&gt;Card charged in the payments database. Stock reserved in the inventory database. Ledger entry recorded in accounting.&lt;/p&gt;

&lt;p&gt;There is no &lt;code&gt;BEGIN&lt;/code&gt; big enough to cover all three, because none of them knows the others exist.&lt;/p&gt;

&lt;p&gt;So when the card charge commits and the inventory reservation then fails because the last unit just sold, nothing rolls back.&lt;/p&gt;

&lt;p&gt;That charge is already durable, on a different machine, in a database that has no idea an order died somewhere else.&lt;/p&gt;

&lt;p&gt;At a few hundred orders a day, you will see this maybe once and fix it by hand. At a few thousand transactions a second across distributed infrastructure, partial failure stops being an incident and starts being a Tuesday.&lt;/p&gt;

&lt;p&gt;This whole shape of problem has a name: the &lt;strong&gt;distributed transaction&lt;/strong&gt;. One logical operation spanning multiple independent databases or services, where every step needs to either succeed together or get cleaned up when something goes sideways.&lt;/p&gt;

&lt;p&gt;The literature offers you two answers. The industry has very firmly picked one of them, and knowing why will save you a genuinely painful quarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The textbook answer: two-phase commit
&lt;/h2&gt;

&lt;p&gt;Two-phase commit is the classic academic solution, formalised decades ago in the &lt;a href="https://pubs.opengroup.org/onlinepubs/009680699/toc.pdf" rel="noopener noreferrer"&gt;X/Open XA specification&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You introduce a new component, a &lt;strong&gt;coordinator&lt;/strong&gt;, whose entire job in life is to make every participant agree on the outcome before anybody makes anything permanent.&lt;/p&gt;

&lt;p&gt;It runs in two phases, which is where the name comes from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase one, prepare.&lt;/strong&gt; The coordinator asks every participant: can you commit this? Each database does the real work, durably records the change so a crash cannot lose it, locks the affected rows so nothing else can touch them, and then answers yes or no.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase two, commit.&lt;/strong&gt; If anyone voted no, the coordinator tells everyone to abort and release their locks. If everyone voted yes, it tells everyone to commit, the locks come off, and you are done.&lt;/p&gt;

&lt;p&gt;What you get out of this is the same strong consistency you had with one database. Every participant agrees before anything is finalised, so there is no visible window where the system is half-done.&lt;/p&gt;

&lt;p&gt;On a whiteboard, this is exactly what you want. It is beautiful. It is also why almost nobody runs it across services.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhsqb5oooagtd99o6tm9k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhsqb5oooagtd99o6tm9k.png" alt="An Older Code Sir meme: an Imperial officer labelled " width="360" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Blocking is the whole problem
&lt;/h2&gt;

&lt;p&gt;2PC is a &lt;strong&gt;blocking protocol&lt;/strong&gt;, and blocking in a distributed system means you have made your availability the product of everybody else's uptime.&lt;/p&gt;

&lt;p&gt;Picture the coordinator collecting all three yes votes. Then, in the gap between collecting them and broadcasting the decision, it dies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffmk68j3ucjx5wp5hg8xa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffmk68j3ucjx5wp5hg8xa.png" alt="Diagram: a coordinator box crossed out in red after collecting all votes, with dashed commit arrows that never arrive at the three participants below, each of which is prepared, has voted yes, and is still holding its row locks" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now look at the participants.&lt;/p&gt;

&lt;p&gt;They cannot commit on their own, because the coordinator might have been about to say abort.&lt;/p&gt;

&lt;p&gt;They cannot abort on their own either, because the coordinator might have been about to say commit, and one of their peers might have already gone through with it.&lt;/p&gt;

&lt;p&gt;So they wait. Holding locks. Indefinitely.&lt;/p&gt;

&lt;p&gt;And every other transaction in your system that wants to touch any of those rows is now queued behind a decision that nobody alive can make.&lt;/p&gt;

&lt;p&gt;Crashes are not even the interesting failure. The boring one is worse.&lt;/p&gt;

&lt;p&gt;A single slow participant stalls the entire transaction. If the ledger service takes ten seconds to answer the prepare message, the payment service and the inventory service both sit there holding locks for ten seconds, doing nothing productive whatsoever.&lt;/p&gt;

&lt;p&gt;Your whole system now moves at the speed of its slowest participant, which in a microservice estate is whichever team deployed most recently.&lt;/p&gt;

&lt;p&gt;Add a network partition and it gets worse still, because the coordinator cannot distinguish "the message did not arrive" from "the message arrived and the reply got lost". There is no safe default to pick.&lt;/p&gt;

&lt;p&gt;Pat Helland's &lt;a href="https://www.cidrdb.org/cidr2007/papers/cidr07p15.pdf" rel="noopener noreferrer"&gt;Life beyond Distributed Transactions&lt;/a&gt; made this argument in 2007 and it has aged extremely well. Distributed transactions across autonomous services do not work at internet scale, so build systems that do not need them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where 2PC actually does live
&lt;/h2&gt;

&lt;p&gt;To be fair to the protocol, 2PC is alive and shipping. It is just not shipping where you would write it yourself.&lt;/p&gt;

&lt;p&gt;It lives &lt;strong&gt;inside&lt;/strong&gt; distributed databases. &lt;a href="https://research.google/pubs/spanner-googles-globally-distributed-database/" rel="noopener noreferrer"&gt;Google Spanner&lt;/a&gt; and &lt;a href="https://docs.yugabyte.com/preview/architecture/transactions/distributed-txns/" rel="noopener noreferrer"&gt;YugabyteDB&lt;/a&gt; both run two-phase commit under the hood to coordinate writes across shards.&lt;/p&gt;

&lt;p&gt;The difference is coupling. In there, the coordinator and the participants are components of one system, with one deployment, one failure model, one team, and a consensus protocol like Paxos or Raft underneath to make the coordinator itself fault-tolerant.&lt;/p&gt;

&lt;p&gt;Crucially, the database is absorbing that complexity so that you, the caller, never see it.&lt;/p&gt;

&lt;p&gt;The moment you try to rebuild that arrangement across independent services with different release trains, different on-call rotations and different opinions about timeouts, every assumption that makes it work in Spanner evaporates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sagas: give up atomicity, keep your weekends
&lt;/h2&gt;

&lt;p&gt;When companies genuinely need to coordinate work across services, the pattern they reach for is the &lt;a href="https://microservices.io/patterns/data/saga.html" rel="noopener noreferrer"&gt;saga&lt;/a&gt;. Uber, Netflix, Amazon and DoorDash all run this in production.&lt;/p&gt;

&lt;p&gt;Sagas start from a deliberately humbler assumption.&lt;/p&gt;

&lt;p&gt;You do not actually need all-or-nothing atomicity spanning five services. You need a way to reliably end up in a consistent state, even when a step blows up along the way.&lt;/p&gt;

&lt;p&gt;So instead of one big locked distributed transaction, you break the work into a chain of independent local transactions. Each service does its piece and commits to its own database, on its own terms, immediately.&lt;/p&gt;

&lt;p&gt;When something fails halfway down the chain, there is no rollback available, because the earlier steps are already committed somewhere else. Instead you run a &lt;strong&gt;compensating action&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is a business-level undo. A refund instead of a rollback. A cancellation instead of an abort.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvjuifbiewlxqb2ioos6a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvjuifbiewlxqb2ioos6a.png" alt="Diagram: three saga steps running left to right, card charged and stock reserved both committed, the ledger write rejected, then two orange compensating actions running right to left: release the stock, then refund the card" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you trade away is strong consistency. What you get is &lt;strong&gt;eventual consistency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The system can genuinely be inconsistent for a few seconds. The customer might see a charge land before they see the refund arrive.&lt;/p&gt;

&lt;p&gt;But it always converges, and nothing is blocked while it converges. Every other transaction in the system keeps flowing the entire time, because no saga step ever holds a lock that a stranger on another machine is responsible for releasing.&lt;/p&gt;

&lt;p&gt;That is the actual trade. Not "correct versus incorrect", but "briefly visible mess" versus "system-wide stall".&lt;/p&gt;

&lt;h2&gt;
  
  
  Choreography or orchestration
&lt;/h2&gt;

&lt;p&gt;Something has to notice the failure and kick off the compensations. Who that something is turns out to be the design decision that matters most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choreography&lt;/strong&gt; is the decentralised option. It is publish and subscribe: each service broadcasts an event when it finishes, and whoever cares picks it up.&lt;/p&gt;

&lt;p&gt;Payments charges the card and publishes &lt;code&gt;CardCharged&lt;/code&gt;. Inventory is listening, reserves the stock, publishes &lt;code&gt;StockReserved&lt;/code&gt;. Ledger picks that up and records the entry. On failure, a service publishes a failure event and upstream services run their own compensations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Orchestration&lt;/strong&gt; is the centralised option. A dedicated orchestrator drives the flow, calling one service at a time and waiting for confirmation before moving on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0m4x6okck6tkh9w1ziq1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0m4x6okck6tkh9w1ziq1.png" alt="Diagram: choreography on the left as a chain of services each reacting to the previous one's event with no central state, orchestration on the right as a single durable orchestrator fanning out to payments, inventory and ledger" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choreography is lovely at three steps. It is miserable at eight.&lt;/p&gt;

&lt;p&gt;Once a dozen services are publishing and reacting to each other, "where is order 1841 right now?" becomes an archaeology project. Which step failed? Which compensations already ran? Did the refund actually go through, or is it still retrying?&lt;/p&gt;

&lt;p&gt;Nobody owns the answer, so you go and read twelve services' logs to reconstruct it.&lt;/p&gt;

&lt;p&gt;Orchestration costs you a central component, and buys you the one thing choreography cannot give you: a single place that knows the state of every in-flight transaction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://temporal.io/" rel="noopener noreferrer"&gt;Temporal&lt;/a&gt;, built by the people behind Uber's &lt;a href="https://cadenceworkflow.io/" rel="noopener noreferrer"&gt;Cadence&lt;/a&gt; workflow engine, and &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/sample-saga-pattern.html" rel="noopener noreferrer"&gt;AWS Step Functions&lt;/a&gt; both exist specifically to be that component.&lt;/p&gt;

&lt;p&gt;And here is the part that matters most, the reason the orchestrator is not just 2PC wearing a new hat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When a saga orchestrator crashes, nothing is holding locks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is durable. It restarts, reads its own state back from its own database, and resumes from exactly where it stopped. Meanwhile the rest of your system never noticed, because no rows were ever frozen waiting on it.&lt;/p&gt;

&lt;p&gt;A dead 2PC coordinator freezes your data. A dead orchestrator just delays one workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compensating actions are where the honesty comes in
&lt;/h2&gt;

&lt;p&gt;"Just undo the previous step" sounds tidy. In practice, it is where sagas get their reputation.&lt;/p&gt;

&lt;p&gt;The card charge committed, the inventory reservation failed, so you refund. Fine. Except that refund is not the invisible cleanup a database rollback gives you.&lt;/p&gt;

&lt;p&gt;The customer sees a charge appear on their card. Then, seconds later, a refund. Their bank probably sent them a push notification for each one. It is correct, and it still generates a support ticket.&lt;/p&gt;

&lt;p&gt;Some actions are worse than visible. Some are simply not undoable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxdxurblz1ingdet6xkww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxdxurblz1ingdet6xkww.png" alt="Hagrid meme captioned " width="360" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You cannot unsend an email. You can send a follow-up saying "ignore that last one", which is a different thing pretending to be the same thing.&lt;/p&gt;

&lt;p&gt;If you fired a webhook at a third party, you can send a cancellation, but you cannot make them process it in time, or at all.&lt;/p&gt;

&lt;p&gt;Every step in your saga needs a compensating action defined up front, and you should be clear-eyed that some of them are inherently imperfect.&lt;/p&gt;

&lt;p&gt;Then there is the recursive problem: &lt;strong&gt;compensations fail too&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What happens when the refund API is down at exactly the moment you need to refund? Now you need retries on your failure handling. And the instant you have retries, you need idempotency, otherwise a flaky network refunds the customer twice and you have invented a new bug that is much harder to explain in a postmortem.&lt;/p&gt;

&lt;p&gt;The trick is to derive the idempotency key from the thing you are undoing, never from the attempt:&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;compensate_charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount_cents&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# keyed on the order, not on this attempt, so ten retries of this
&lt;/span&gt;    &lt;span class="c1"&gt;# compensation collapse into exactly one refund on the customer's card
&lt;/span&gt;    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refund:order:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Refund&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;payment_intent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;lookup_payment_intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;amount_cents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Generate a fresh UUID per attempt and you have an idempotency key that guarantees nothing.&lt;/p&gt;

&lt;p&gt;The uncomfortable summary is that your unhappy path needs the same reliability engineering as your happy path. Most teams budget for one of those.&lt;/p&gt;
&lt;h2&gt;
  
  
  The dual write problem, quietly waiting behind all of this
&lt;/h2&gt;

&lt;p&gt;Even with perfect compensation logic, there is one more failure mode that catches teams off guard, and it is the one I would bet money is already lurking in your codebase.&lt;/p&gt;

&lt;p&gt;When the payment service finishes charging the card, it has to do two things. Save the result to its own database, and publish an event so the next step knows to run.&lt;/p&gt;

&lt;p&gt;Those are two writes, to two different systems, with no shared transaction between them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd35ynn4cpksmrpu7c19u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd35ynn4cpksmrpu7c19u.png" alt="Drowning High Five meme: one hand labelled " width="360" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the database write succeeds and the event publish fails, the next step never fires and the saga stalls silently. No error, no compensation, just an order sitting in &lt;code&gt;charged&lt;/code&gt; forever until a customer emails you.&lt;/p&gt;

&lt;p&gt;If the event publishes and the database write fails, downstream services are now reacting enthusiastically to something that never happened.&lt;/p&gt;

&lt;p&gt;The fix is the &lt;a href="https://microservices.io/patterns/data/transactional-outbox.html" rel="noopener noreferrer"&gt;transactional outbox&lt;/a&gt;, and it is one of those patterns that feels like cheating the first time you see it.&lt;/p&gt;

&lt;p&gt;Stop treating the event as a separate write. Put it in the same database, in the same transaction, as the data.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;
   &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'charged'&lt;/span&gt;
 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1841&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;outbox&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aggregate_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1841&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'CardCharged'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'{"order_id":1841,"amount_cents":4999}'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;One local transaction. Both rows commit, or neither does. The dual write is gone, because there is no longer a second write to fail.&lt;/p&gt;

&lt;p&gt;A separate background relay then reads that outbox table and publishes to the broker. It can tail the database's own transaction log via change data capture, which is what &lt;a href="https://debezium.io/documentation/reference/stable/transformations/outbox-event-router.html" rel="noopener noreferrer"&gt;Debezium&lt;/a&gt; does, or it can just poll the table on an interval, which is unglamorous and works fine at most scales.&lt;br&gt;
&lt;/p&gt;
&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[Card charged successfully] --&amp;gt; B[BEGIN local transaction]
    B --&amp;gt; C[Write the payment row]
    C --&amp;gt; D[Write the event into the outbox table]
    D --&amp;gt; E[COMMIT: both rows, or neither]
    E --&amp;gt; F{How does the relay&amp;lt;br/&amp;gt;pick it up?}
    F --&amp;gt;|CDC, tail the WAL| G[Publish to the message broker]
    F --&amp;gt;|or just poll the table| G
    G --&amp;gt; H[Next saga step runs]

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef start fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
    classDef db fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef out fill:#6ea8ff,stroke:#2f5fbf,color:#1a1a1a

    class A start
    class F decision
    class B,C,D,E db
    class G,H out&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Note that this gives you at-least-once delivery, not exactly-once. The relay can crash after publishing and before marking the row as sent, so the event goes out twice.&lt;/p&gt;

&lt;p&gt;Which brings us back to idempotency. Every consumer in your saga needs to handle seeing the same event twice, because it will.&lt;/p&gt;
&lt;h2&gt;
  
  
  So what should you actually build?
&lt;/h2&gt;

&lt;p&gt;Before either of these patterns, ask the question that people skip because it feels like a cop-out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do you need a distributed transaction at all?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you can draw your service boundaries so that data which changes together lives in the same database, do that. Move the inventory and ledger tables next to the payments table if they are always updated together.&lt;/p&gt;

&lt;p&gt;A local transaction is simpler, faster and more reliable than any distributed thing you can build on top of it. This is always the best answer when you can get away with it, and it is enormously easier to get right up front than to retrofit two years and forty services later.&lt;br&gt;
&lt;/p&gt;
&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[One operation spans several services] --&amp;gt; B{Can that data live&amp;lt;br/&amp;gt;in a single database?}
    B --&amp;gt;|Yes| C[Just use a local transaction]
    B --&amp;gt;|No| D{Is eventual consistency&amp;lt;br/&amp;gt;acceptable here?}
    D --&amp;gt;|No| E[Put it in a distributed SQL database&amp;lt;br/&amp;gt;Spanner or YugabyteDB]
    D --&amp;gt;|Yes| F{Many steps, branching,&amp;lt;br/&amp;gt;or tricky compensations?}
    F --&amp;gt;|No| G[Saga with choreography]
    F --&amp;gt;|Yes| H[Saga with orchestration]
    H --&amp;gt; I[Temporal or AWS Step Functions]
    G --&amp;gt; J[Transactional outbox&amp;lt;br/&amp;gt;+ idempotent consumers]
    H --&amp;gt; J

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef start fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
    classDef good fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef alt fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a
    classDef tool fill:#6ea8ff,stroke:#2f5fbf,color:#1a1a1a

    class A start
    class B,D,F decision
    class C,H good
    class G,E alt
    class I,J tool&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;If you truly cannot keep it in one database, you are writing a saga. That part is not really up for debate anymore.&lt;/p&gt;

&lt;p&gt;The remaining question is which flavour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choreography&lt;/strong&gt; if the flow is short, the services are genuinely independent, and nobody needs a central view of where a transaction stands. An order placed event triggering an email and a push notification is a perfect fit, and wiring up an orchestrator for it would be theatre.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Orchestration&lt;/strong&gt; for anything with branching logic, more than a handful of steps, or compensation logic you would rather define in one file than scatter across six repos. Most teams end up here eventually, and the tooling has got good enough that starting here is no longer expensive.&lt;/p&gt;

&lt;p&gt;And if eventual consistency is genuinely unacceptable for some specific slice of your data, put that slice in a distributed database that handles strong consistency internally. That is a completely different proposition from hand-rolling 2PC across services you do not control.&lt;/p&gt;
&lt;h2&gt;
  
  
  The shape that keeps winning
&lt;/h2&gt;

&lt;p&gt;Strip it all back and the production answer at basically every company operating at scale looks the same.&lt;/p&gt;

&lt;p&gt;A saga, orchestrated by something durable. Idempotent operations at every step, so retries are always safe. A transactional outbox, so events are as reliable as the database writes they describe.&lt;/p&gt;

&lt;p&gt;It means accepting that your system is briefly, visibly inconsistent sometimes.&lt;/p&gt;

&lt;p&gt;That is not a compromise the industry stumbled into. It is one it chose, on purpose, after watching two-phase commit deadlock enough production databases to make the decision for everyone.&lt;/p&gt;

&lt;p&gt;The database gave you atomicity for free. The moment you split it, the bill came due, and sagas are simply the cheapest way anyone has found to pay it.&lt;/p&gt;



&lt;p&gt;&lt;em&gt;&lt;br&gt;
Your team's attention is limited, and the deluge of AI-generated code is making it harder to keep production secure and reliable without slowing you down.&lt;/em&gt;&lt;/p&gt;
&lt;em&gt;

&lt;p&gt;I'm building &lt;strong&gt;LiveReview&lt;/strong&gt;, a blast-radius aware AI code review built for your business-critical systems.&lt;/p&gt;

&lt;p&gt;Instead of presenting every diff with equal emphasis, &lt;strong&gt;LiveReview scores each change by blast radius — how far its impact reaches through your call graph — so you can focus attention where it actually matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spend code review effort where business risk is highest — not spread evenly across every diff.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;
        LiveReview
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Blast-Radius Aware AI Code Review for Business-Critical Systems
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/png/logo-with-text.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fpng%2Flogo-with-text.png" alt="LiveReview" height="80"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml" rel="noopener noreferrer"&gt;&lt;img alt="mcp-testcases.yml" title="mcp-testcases.yml: MCP integration test suite" src="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml/badge.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LiveReview: Risk-Aware AI Code Review for Business-Critical Systems&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;LiveReview is an AI code reviewer that scores every hunk of a diff by &lt;strong&gt;blast radius&lt;/strong&gt;: how far a change reaches through your call graph, how much persistent state it touches, and how well-tested it is. A 3-line change to a shared auth check can outrank a 300-line UI tweak. Your team's attention goes to the highest-risk code first, not spread evenly across every diff.&lt;/p&gt;


  
    
    &lt;span class="m-1"&gt;blast-radius-demo.mp4&lt;/span&gt;
  

  

  


&lt;p&gt;&lt;i&gt;LiveReview's Blast Radius &amp;amp; Review Priority scoring, live in the diff viewer.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;The exact math, not a black box&lt;/th&gt;

&lt;th&gt;Visualize blast radius at a glance&lt;/th&gt;

&lt;th&gt;Every factor that feeds the score&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-3.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-3.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-4.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-4.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-2.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-2.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;&lt;/p&gt;

How does Blast Radius scoring work? (a more technical explanation)

&lt;p&gt;&lt;strong&gt;Here's the goal:&lt;/strong&gt;&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;A 3-line fix in a function used by 40 other files, that also writes to a database, should score high.&lt;/li&gt;

&lt;li&gt;A 300-line UI change in one file, fully covered by tests…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;b&gt;Click below to try LiveReview with your codebase:&lt;/b&gt;&lt;/p&gt;

&lt;/em&gt;&lt;p&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hexmos.com/livereview" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvls0pq7nymbrll98je6s.png" alt="LiveReview Banner" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>microservices</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>Do You Really Need to Be in the Top 0.1% of Your Field?</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Sun, 27 Sep 2026 17:52:14 +0000</pubDate>
      <link>https://dev.to/lovestaco/do-you-really-need-to-be-in-the-top-01-of-your-field-147</link>
      <guid>https://dev.to/lovestaco/do-you-really-need-to-be-in-the-top-01-of-your-field-147</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. &lt;a href="https://github.com/HexmosTech/LiveReview/" rel="noopener noreferrer"&gt;Star us&lt;/a&gt; to help devs discover the project, give it a try, and share your feedback to help improve the product.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Today I found a post on LinkedIn that hit a nerve.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If you're not best at what you do, nothing matters bhai !

I literally mean it, coz I felt it myself.

This is not some post which I am posting coz I am happy, sad or any XYZ emotion.

If you're not on top, you're just an average guy, and trust me no one respects an average guy.

If you're average, just keep your head down, and keep working until you can say,
"Yes, I belong to 0.1% of my field".

Not to motivate, or demotivate. It's just what I felt.

Keep GRINDING bhai!!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I read it twice.&lt;/p&gt;

&lt;p&gt;The first read got me fired up, which is exactly what a post like that is built to do.&lt;/p&gt;

&lt;p&gt;The second read left me with a question I could not shake: is this actually true? Do I need to be in the top 0.1% of my field for any of my work to matter?&lt;/p&gt;

&lt;p&gt;So I went looking. Not for motivational quotes, but for what people who genuinely got to the top of their industries say about how they kept score along the way.&lt;/p&gt;

&lt;p&gt;What I found changed how I read that post entirely.&lt;/p&gt;
&lt;h2&gt;
  
  
  The problem with "top 0.1%" is not the ambition
&lt;/h2&gt;

&lt;p&gt;Let me be clear about what I am not saying.&lt;/p&gt;

&lt;p&gt;I am not saying aim lower. The intensity in that post is real and I respect it.&lt;/p&gt;

&lt;p&gt;The problem is not the height of the bar. The problem is that "top 0.1% of my field" is a terrible thing to point your daily effort at, because you cannot actually do it.&lt;/p&gt;

&lt;p&gt;Try it right now. Open your calendar for tomorrow and schedule "be in the top 0.1%".&lt;/p&gt;

&lt;p&gt;You can't. There is no task there. There is no first step. There is nothing you can start at 9am and finish by lunch.&lt;/p&gt;

&lt;p&gt;You can schedule "read the Raft paper", or "rewrite that gnarly service with tests this time", or "review three PRs properly instead of rubber stamping them". Those are things.&lt;/p&gt;

&lt;p&gt;"Top 0.1%" is not a thing you do. It is a thing that gets said about you, later, by other people, after a lot of stuff outside your control goes your way.&lt;/p&gt;
&lt;h2&gt;
  
  
  Buffett's answer: which scorecard are you using?
&lt;/h2&gt;

&lt;p&gt;The first thing I ran into was &lt;a href="https://fs.blog/the-inner-scorecard/" rel="noopener noreferrer"&gt;Warren Buffett's idea of the Inner Scorecard&lt;/a&gt;, from Alice Schroeder's biography &lt;em&gt;The Snowball&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Buffett frames it as a single question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The big question about how people behave is whether they've got an Inner Scorecard or an Outer Scorecard. It helps if you can be satisfied with an Inner Scorecard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then he makes it concrete with a test that is hard to wriggle out of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lookit. Would you rather be the world's greatest lover, but have everyone think you're the world's worst lover? Or would you rather be the world's worst lover but have everyone think you're the world's greatest lover?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Swap in your own field and answer honestly.&lt;/p&gt;

&lt;p&gt;Would you rather be a genuinely excellent engineer that nobody has heard of, or a mediocre one that everybody thinks is excellent?&lt;/p&gt;

&lt;p&gt;Most of us want to say the first one. Very few of us actually behave like it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpchdy4o04ejz5vggh7b2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpchdy4o04ejz5vggh7b2.png" alt="Diagram comparing the Outer Scorecard, graded by everyone else and containing rank, likes, titles, salary, timing and luck, against the Inner Scorecard, graded by yourself and containing effort, intensity, training, consistency and honesty" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Outer Scorecard is your rank, your title, your follower count, your comp band, what people say about you when you leave the room.&lt;/p&gt;

&lt;p&gt;The Inner Scorecard is your effort, your intensity, your training, your consistency, and whether you were honest with yourself about all four.&lt;/p&gt;

&lt;p&gt;Here is what makes the difference practical rather than philosophical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Outer Scorecard moves even when you do nothing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Somebody more talented joins your field and you drop a slot. A hiring freeze lifts somewhere else and you drop two more. A guy who is worse than you posts better and passes you anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Inner Scorecard only moves when you move it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nobody can take a day of real work away from you. Nobody can devalue the hour you spent actually understanding something instead of pretending to.&lt;/p&gt;

&lt;p&gt;Buffett did not pick this up from a book, by the way. He got it from his father Howard, who he described as "a hundred percent Inner Scorecard guy", a congressman who kept voting his conscience into losing causes and never seemed bothered that it cost him.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvu4lnynsqtjk4iciwiq2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvu4lnynsqtjk4iciwiq2.png" alt="Meme: An Older Code Sir But It Checks Out, captioned taking life advice from a man who has been compounding since 1956" width="360" height="194"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Bezos says the same thing in engineer language
&lt;/h2&gt;

&lt;p&gt;Buffett's version is about character. If that feels a bit soft, the same idea shows up in a much more mechanical form.&lt;/p&gt;

&lt;p&gt;Jeff Bezos, in the &lt;a href="https://www.sec.gov/Archives/edgar/data/1018724/000119312510082914/dex991.htm" rel="noopener noreferrer"&gt;2009 Amazon shareholder letter&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We take these financial outputs seriously, but we believe that focusing our energy on the controllable inputs to our business is the most effective way to maximize financial outputs over time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And he is blunt about why:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I have no idea how to drive up the stock price. I can't manage that directly. It's not a controllable input. But I can make your picking algorithms more efficient, and that will reduce cost structure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That last sentence is the whole thing.&lt;/p&gt;

&lt;p&gt;The stock price is an output. It is the score other people give you. You cannot touch it.&lt;/p&gt;

&lt;p&gt;Picking efficiency in a warehouse is an input. You can walk over there tomorrow and improve it.&lt;/p&gt;

&lt;p&gt;The method Bezos describes is just working backwards until you hit something you can actually put your hands on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You keep working backwards until you get to something that's controllable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So let's run that on "top 0.1% of my field".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpu93g38tq6r6boajdfz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqpu93g38tq6r6boajdfz.png" alt="Diagram showing the goal top 0.1 percent of your field at the top as an uncontrollable output, decomposing into partly controllable middle factors like reputation, who gets the chance, timing and luck, and then into fully controllable inputs like hours of deep focused work, hard problems shipped, feedback asked for and used, and showing up again tomorrow" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Top 0.1% is the output. What feeds it? Reputation, who happened to give you a shot, and the timing of the market you walked into. Those are half yours at best.&lt;/p&gt;

&lt;p&gt;Keep going backwards. What feeds &lt;em&gt;those&lt;/em&gt;? Hours of deep work. Hard problems you actually shipped instead of talking about. Feedback you asked for and then used. Showing up again the next day when it stopped being fun.&lt;/p&gt;

&lt;p&gt;That bottom row is where you live. You can do every single one of those tomorrow, without anyone's permission, and nothing in the world can stop you.&lt;/p&gt;

&lt;p&gt;Once you write it out like that, the top row stops looking like a goal and starts looking like a weather report.&lt;/p&gt;
&lt;h2&gt;
  
  
  Both men are describing the same trap
&lt;/h2&gt;

&lt;p&gt;Read them together and it is one idea wearing two outfits.&lt;/p&gt;

&lt;p&gt;Buffett: do not let other people hold your scorecard.&lt;/p&gt;

&lt;p&gt;Bezos: do not aim at a number you cannot move.&lt;/p&gt;

&lt;p&gt;The failure mode is identical either way, and I have watched it happen to good engineers.&lt;/p&gt;

&lt;p&gt;You pick an outer metric. Staff title, follower count, top 0.1%, whatever it is.&lt;/p&gt;

&lt;p&gt;You work hard. The metric does not move, because it depends on six things that have nothing to do with you.&lt;/p&gt;

&lt;p&gt;Now you have a problem. The honest read is "the inputs were fine, the output lagged". But the outer scorecard does not offer that read. It just shows you a number that did not go up.&lt;/p&gt;

&lt;p&gt;So you conclude the work was worthless. You either burn out or you start optimising for the thing that actually moves the number, which is almost never the work itself.&lt;/p&gt;

&lt;p&gt;That is how you end up with people who post about engineering more than they engineer.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I actually do with this
&lt;/h2&gt;

&lt;p&gt;I am not going to pretend I have solved this. I check my own outer scorecard more than I would like to admit.&lt;/p&gt;

&lt;p&gt;But one small thing has helped, and it is basically Bezos with the corporate serial numbers filed off.&lt;/p&gt;

&lt;p&gt;At the end of the day, I do not ask "am I getting closer to the top 0.1%".&lt;/p&gt;

&lt;p&gt;I ask: &lt;strong&gt;did today's inputs deserve a good output?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Did I do real work, or did I do busy work that looked like real work?&lt;br&gt;
Did I learn something I did not know this morning?&lt;br&gt;
Was I honest about the thing I got stuck on, or did I quietly route around it?&lt;/p&gt;

&lt;p&gt;Some days the answer is no. That is fine, it is information, and it is information I can act on tomorrow.&lt;/p&gt;

&lt;p&gt;The important part is that this question has an answer I own. "Am I top 0.1% yet" does not.&lt;/p&gt;
&lt;h2&gt;
  
  
  Keep the grind, move the scoreboard
&lt;/h2&gt;

&lt;p&gt;None of this is an argument for wanting less.&lt;/p&gt;

&lt;p&gt;The grind part is real, and if you have the intensity for it, that is a genuine advantage over most people. Do not talk yourself out of it.&lt;/p&gt;

&lt;p&gt;Aiming for the top 0.1% is a fine direction to have. It is just a bad thing to measure yourself by.&lt;/p&gt;

&lt;p&gt;Being in the top 0.1% is an output. It depends on a lot of things we don't control, like timing, competition, and luck.&lt;/p&gt;

&lt;p&gt;The inputs are what we do control: our effort, our training, our intensity and consistency.&lt;/p&gt;

&lt;p&gt;If we get those right, over time the output takes care of itself.&lt;/p&gt;

&lt;p&gt;So keep the intensity. All of it.&lt;/p&gt;

&lt;p&gt;Just move it off the scoreboard and onto the thing that actually feeds the scoreboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frqgnpwretf8e820a6fw2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frqgnpwretf8e820a6fw2.png" alt="Meme: Change My Mind guy at a table with a sign reading the top 0.1 percent is a result, not a plan" width="360" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I want to hear from you, because I know this community is full of people quietly keeping score on themselves.&lt;/p&gt;

&lt;p&gt;Which scorecard are you running on right now?&lt;/p&gt;

&lt;p&gt;And if you switched from outer to inner at some point, what was the thing that made you switch?&lt;/p&gt;



&lt;p&gt;&lt;em&gt;&lt;br&gt;
Your team's attention is limited, and the deluge of AI-generated code is making it harder to keep production secure and reliable without slowing you down.&lt;/em&gt;&lt;/p&gt;
&lt;em&gt;

&lt;p&gt;I'm building &lt;strong&gt;LiveReview&lt;/strong&gt;, a blast-radius aware AI code review built for your business-critical systems.&lt;/p&gt;

&lt;p&gt;Instead of presenting every diff with equal emphasis, &lt;strong&gt;LiveReview scores each change by blast radius — how far its impact reaches through your call graph — so you can focus attention where it actually matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spend code review effort where business risk is highest — not spread evenly across every diff.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;
        LiveReview
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Blast-Radius Aware AI Code Review for Business-Critical Systems
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/png/logo-with-text.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fpng%2Flogo-with-text.png" alt="LiveReview" height="80"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml" rel="noopener noreferrer"&gt;&lt;img alt="mcp-testcases.yml" title="mcp-testcases.yml: MCP integration test suite" src="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml/badge.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LiveReview: Risk-Aware AI Code Review for Business-Critical Systems&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;LiveReview is an AI code reviewer that scores every hunk of a diff by &lt;strong&gt;blast radius&lt;/strong&gt;: how far a change reaches through your call graph, how much persistent state it touches, and how well-tested it is. A 3-line change to a shared auth check can outrank a 300-line UI tweak. Your team's attention goes to the highest-risk code first, not spread evenly across every diff.&lt;/p&gt;


  
    
    &lt;span class="m-1"&gt;blast-radius-demo.mp4&lt;/span&gt;
  

  

  


&lt;p&gt;&lt;i&gt;LiveReview's Blast Radius &amp;amp; Review Priority scoring, live in the diff viewer.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;The exact math, not a black box&lt;/th&gt;

&lt;th&gt;Visualize blast radius at a glance&lt;/th&gt;

&lt;th&gt;Every factor that feeds the score&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-3.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-3.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-4.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-4.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-2.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-2.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;&lt;/p&gt;

How does Blast Radius scoring work? (a more technical explanation)

&lt;p&gt;&lt;strong&gt;Here's the goal:&lt;/strong&gt;&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;A 3-line fix in a function used by 40 other files, that also writes to a database, should score high.&lt;/li&gt;

&lt;li&gt;A 300-line UI change in one file, fully covered by tests…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;b&gt;Click below to try LiveReview with your codebase:&lt;/b&gt;&lt;/p&gt;

&lt;/em&gt;&lt;p&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hexmos.com/livereview" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvls0pq7nymbrll98je6s.png" alt="LiveReview Banner" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>productivity</category>
      <category>discuss</category>
      <category>motivation</category>
    </item>
    <item>
      <title>GFS Cost More Than 50 Cents a Run. Now Each Run Costs 4 Cents</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Sat, 26 Sep 2026 18:02:42 +0000</pubDate>
      <link>https://dev.to/lovestaco/gfs-cost-more-than-50-cents-a-run-now-it-costs-4-cents-3lf8</link>
      <guid>https://dev.to/lovestaco/gfs-cost-more-than-50-cents-a-run-now-it-costs-4-cents-3lf8</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. &lt;a href="https://github.com/HexmosTech/LiveReview/" rel="noopener noreferrer"&gt;Star us&lt;/a&gt; to help devs discover the project, give it a try, and share your feedback to help improve the product.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The bill was 50 cents a run.&lt;/p&gt;

&lt;p&gt;Not a month. Not a thousand runs. One run.&lt;/p&gt;

&lt;p&gt;Paste a design proposal in, get back a grounded review with precedent from our shelf of books and internal postmortems, and watch half a dollar evaporate.&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/lovestaco/cheap-rag-in-go-with-gemini-file-search-no-vector-db-two-calls-one-hosted-store-4kb5" class="crayons-story__hidden-navigation-link"&gt;Cheap RAG in Go with Gemini File Search: no vector DB, two calls, one hosted store&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/lovestaco/cheap-rag-in-go-with-gemini-file-search-no-vector-db-two-calls-one-hosted-store-4kb5" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;Comments warn about API key store isolation quirks&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/lovestaco" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002302%2F9dbe5057-f6da-4c08-9b5d-37fe9d281476.png" alt="lovestaco profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/lovestaco" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Athreya aka Maneshwar
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Athreya aka Maneshwar
                
                
              
              &lt;div id="story-author-preview-content-4710440" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/lovestaco" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1002302%2F9dbe5057-f6da-4c08-9b5d-37fe9d281476.png" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Athreya aka Maneshwar&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/lovestaco/cheap-rag-in-go-with-gemini-file-search-no-vector-db-two-calls-one-hosted-store-4kb5" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 22&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/lovestaco/cheap-rag-in-go-with-gemini-file-search-no-vector-db-two-calls-one-hosted-store-4kb5" id="article-link-4710440"&gt;
          Cheap RAG in Go with Gemini File Search: no vector DB, two calls, one hosted store
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/go"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;go&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/rag"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;rag&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gemini"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gemini&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/lovestaco/cheap-rag-in-go-with-gemini-file-search-no-vector-db-two-calls-one-hosted-store-4kb5" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;52&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/lovestaco/cheap-rag-in-go-with-gemini-file-search-no-vector-db-two-calls-one-hosted-store-4kb5#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              14&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            14 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;Part 1 was the happy version of this story: a Go binary, Gemini's hosted File Search, no vector database, and a free tier that made the whole thing look like a free lunch.&lt;/p&gt;

&lt;p&gt;Then the free tier ran out and the paid rates arrived, and the free lunch turned out to be a tasting menu.&lt;/p&gt;

&lt;p&gt;Five people on the team, several runs a day each, and a number that scaled with how hard the tool was thinking.&lt;/p&gt;

&lt;p&gt;This post is what we replaced it with, what we measured, and the one component we built, benchmarked, and then deleted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The word "free" was doing a lot of work
&lt;/h2&gt;

&lt;p&gt;Here is the thing the pricing page tells you about File Search, and it is all true.&lt;/p&gt;

&lt;p&gt;Storage is free. Query-time embeddings are free. You pay once at indexing time.&lt;/p&gt;

&lt;p&gt;Here is the thing it does not put in bold.&lt;/p&gt;

&lt;p&gt;The search is performed &lt;strong&gt;by a model&lt;/strong&gt;. It is a tool the model calls mid-answer. Which means the retrieved chunks land in that model's context as ordinary input tokens, and the model reasons its way through them before replying.&lt;/p&gt;

&lt;p&gt;And on &lt;a href="https://ai.google.dev/gemini-api/docs/pricing" rel="noopener noreferrer"&gt;Gemini 3.5 Flash&lt;/a&gt;, reasoning costs $9.00 per million output tokens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ferpi3ttmyvlkia96ued0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ferpi3ttmyvlkia96ued0.png" alt="Diagram: anatomy of one File Search call. 22,053 uncached input tokens at $1.50 per million is $0.033, 7,903 cached input tokens at $0.15 is $0.001, and 9,529 output tokens at $9.00 is $0.086, for about $0.12 on one search call. 72 percent of the cost is output and 8,493 of the 9,529 output tokens were the model thinking. Below, cost per run as the pipeline changed: $0.70 when every draft ran its own search, $0.12 with one shared search, $0.045 with local retrieval and DeepSeek" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at that output row.&lt;/p&gt;

&lt;p&gt;9,529 tokens out, of which 8,493 were the model thinking. For a call whose entire job is "find the relevant passages and hand them over."&lt;/p&gt;

&lt;p&gt;We paid a reasoning model to reason about which paragraphs to copy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz068hxdpykoazk3c3uxe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz068hxdpykoazk3c3uxe.png" alt="Inigo Montoya meme: free retrieval, you keep using that word, I do not think it means what you think it means" width="360" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first fix was structural and it helped a lot. The draft step used to do its own search, which meant three drafts meant three searches. Splitting search out so one search feeds every draft took a run from about $0.70 to about $0.12.&lt;/p&gt;

&lt;p&gt;Six times cheaper, and still the wrong shape.&lt;/p&gt;

&lt;p&gt;Because the expensive part was never the searching. It was the thinking attached to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval does not need a brain.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It needs an index, a similarity function, and a tiebreaker. Every one of those is a thing you can run on a laptop while it charges.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we swapped in
&lt;/h2&gt;

&lt;p&gt;A local &lt;a href="https://www.trychroma.com/" rel="noopener noreferrer"&gt;Chroma&lt;/a&gt; store, embedded with &lt;a href="https://huggingface.co/Qwen/Qwen3-Embedding-0.6B" rel="noopener noreferrer"&gt;Qwen3-Embedding-0.6B&lt;/a&gt;, searched with dense vectors plus BM25, fused with Reciprocal Rank Fusion.&lt;/p&gt;

&lt;p&gt;Every model call moved to DeepSeek V4 Flash on Atlas Cloud at $0.14 in and $0.28 out per million. &lt;/p&gt;

&lt;p&gt;Roughly ten times cheaper per token than what we were on, with a 1M context window and JSON mode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqe4i7syv8za6iyll3yvm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqe4i7syv8za6iyll3yvm.png" alt="Diagram: what runs where. Billed on Atlas with DeepSeek V4 Flash: call 1 analyze producing a pattern and 3 queries, call 2 drafting three times at once at temperature 0.7, call 3 reviewing. Free on your own machine: the commentor Go binary handling checks, citations and SQLite, rag/serve.py as a child process on port 8791 holding Qwen3-Embedding and BM25, and db/chroma with 3,639 chunks in 69 MB committed to git. Queries come down from call 1, 12 passages go up to the draft verbatim. Retrieval takes 0.5s and costs nothing" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The retrieval service is Python, running as a child process of the Go binary, on a port nobody else talks to. It starts with &lt;code&gt;make run&lt;/code&gt; and dies with it.&lt;/p&gt;

&lt;p&gt;I did try to talk myself out of that boundary.&lt;/p&gt;

&lt;p&gt;Go has no mature CUDA story. The honest route is exporting the model to ONNX and linking a Go runtime through cgo, matching driver and CUDA and cuDNN versions exactly. PyTorch is the path a million people have already debugged, including on WSL2, where CUDA passthrough breaks in creative ways.&lt;/p&gt;

&lt;p&gt;Adding a process boundary was cheaper than adding a whole new class of "works on my machine."&lt;/p&gt;

&lt;h2&gt;
  
  
  Chunking is where the accuracy actually lives
&lt;/h2&gt;

&lt;p&gt;Everyone talks about which embedding model to pick. Almost nobody talks about what you feed it, and that is where the wins were.&lt;/p&gt;

&lt;p&gt;Our corpus is 71 markdown files, and most of them are books that used to be PDFs. PDFs converted to markdown are full of things that are not text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F00mqg1az1l0byn30c3j1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F00mqg1az1l0byn30c3j1.png" alt="Diagram: one page of a converted book showing a page number, a running header repeated on every page, a picture-text block and a hyphen line break, all marked for removal, leaving the real sentence. The stored chunk is about 300 words with a hard cap of 450, two sentences of overlap carried forward, and a real section heading starts a new one. The embedded string is the title, kind and heading path followed by the text, so a chunk that only says " width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The cleaner strips the converter's own footer, lines that are only a page number, picture-text blocks, and hyphenated line breaks that split a word across two lines.&lt;/p&gt;

&lt;p&gt;The one that surprised me is running headers.&lt;/p&gt;

&lt;p&gt;A converted book repeats the chapter title at the top of every single page, usually promoted to a markdown heading. &lt;/p&gt;

&lt;p&gt;Left in, it shreds the text into confetti, and a passage about paper reactors comes back with "Ship Project and Civilian Power" wedged into the middle of a sentence.&lt;/p&gt;

&lt;p&gt;The rule that fixed it is embarrassingly simple: a short line that appears five or more times in one file is page furniture, not prose.&lt;/p&gt;

&lt;p&gt;Then the chunking. About 300 words, hard cap 450, split on sentence boundaries, with two sentences carried into the next chunk so a quote that straddles a boundary survives in at least one piece. A real section heading starts a new chunk, once the current one has enough in it to stand alone.&lt;/p&gt;

&lt;p&gt;And then the part I would steal even if you take nothing else from this post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embed more than you store.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The chunk you store is the text the draft is allowed to quote. The string you embed has a header glued on top of it:&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;EMBED_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Qwen/Qwen3-Embedding-0.6B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# what goes into the embedding, per chunk
&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;) › &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;heading_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# and the query side gets an instruction, because Qwen3-Embedding is
# instruction-tuned on queries only. documents are embedded as they are.
&lt;/span&gt;&lt;span class="n"&gt;QUERY_PROMPT&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;Instruct: Given an abstract pattern or principle, retrieve historical cases, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;documented examples, and named principles from books and essays that show &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the same pattern&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Query: &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;A chunk in the middle of chapter nine might only say "he decided otherwise."&lt;/p&gt;

&lt;p&gt;With the header, that vector still knows it is Rickover, in a book about Rickover, in a section about paper reactors. Without it, it is a pronoun floating in space.&lt;/p&gt;

&lt;p&gt;The query instruction is the Part 1 idea, "search with the pattern, not the post," pushed one layer down into the embedding itself. The corpus is cases. The queries are patterns. Saying so out loud to a model that was trained to listen costs nothing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Dense finds the paraphrase, BM25 finds the name
&lt;/h2&gt;

&lt;p&gt;Vector search is great at "these two paragraphs mean the same thing" and oddly bad at "this paragraph contains the word Rickover."&lt;/p&gt;

&lt;p&gt;Keyword search is the reverse.&lt;/p&gt;

&lt;p&gt;So we run both, take 40 candidates each, and fuse them.&lt;br&gt;
&lt;/p&gt;
&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    Q[one query from call 1] --&amp;gt; DN[dense top 40, Qwen3-Embedding]
    Q --&amp;gt; BM[BM25 top 40, title + text]
    DN --&amp;gt; RRF[Reciprocal Rank Fusion, k=60]
    BM --&amp;gt; RRF
    RRF --&amp;gt; SEL[keep the best 4 per query]
    SEL --&amp;gt; CAP{2 chunks from this file already?}
    CAP -- yes --&amp;gt; SKIP[skip, so no book dominates]
    CAP -- no --&amp;gt; KEEP[keep it]
    KEEP --&amp;gt; RR[round-robin merge, 3 queries]
    SKIP --&amp;gt; RR
    RR --&amp;gt; DUP{near-duplicate of one picked?}
    DUP -- yes --&amp;gt; DROP[drop: a post synced twice]
    DUP -- no --&amp;gt; OUT[12 passages go to the draft]

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef start    fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
    classDef dense    fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a
    classDef lex      fill:#6ea8ff,stroke:#2f5fc4,color:#1a1a1a
    classDef good     fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef bad      fill:#ff9a5c,stroke:#c4602a,color:#1a1a1a

    class CAP,DUP decision
    class Q start
    class DN,RRF dense
    class BM,SEL,RR lex
    class KEEP,OUT good
    class SKIP,DROP bad&lt;/code&gt;&lt;/pre&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;hybrid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CANDIDATES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&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;ranked&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lexical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;score&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="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RRF_K&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])[:&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That is the whole fusion. Six lines.&lt;/p&gt;

&lt;p&gt;The reason it works is that it never compares the two scores. A cosine similarity of 0.82 and a BM25 score of 14.3 have nothing to say to each other. Ranks do. &lt;/p&gt;

&lt;p&gt;A document at position 3 in both lists beats one that is first in a single list, and the constant &lt;code&gt;k&lt;/code&gt; (60 is the number the literature settled on) keeps the top of each list from steamrolling everything else.&lt;/p&gt;

&lt;p&gt;BM25 indexes the title and heading alongside the text, same as the embedding header does, so naming a book in your query actually finds pages from that book.&lt;/p&gt;

&lt;p&gt;A few rules keep the final twelve honest. At most two chunks per file per query, so one 400-page book cannot fill every slot. Queries merge round robin, so each of the three contributes its best passage before any of them gets its fourth. &lt;/p&gt;

&lt;p&gt;And anything with a word-overlap above 0.6 against something already picked gets dropped, because our blog corpus has a couple of posts that got synced twice and they were politely returning themselves as two independent sources.&lt;/p&gt;
&lt;h2&gt;
  
  
  The reranker that got fired
&lt;/h2&gt;

&lt;p&gt;Standard advice says: retrieve broadly, then rerank with a cross-encoder. So we did that, with &lt;code&gt;bge-reranker-v2-m3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then a run took six and a half minutes and I went looking.&lt;/p&gt;

&lt;p&gt;The retrieval service's own log had it in one line: &lt;code&gt;search: 3 queries -&amp;gt; 8 chunks in 117.5s&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Three queries times 40 candidates is 120 cross-encoder forward passes on a 4 GB GTX 1650 that is also drawing the desktop. It was not thrashing. It was just honest work on unfit hardware.&lt;/p&gt;

&lt;p&gt;Before ripping it out, we measured. The golden set builds itself out of finished sessions: take Call 1's retrieval queries, pair them with the files the accepted draft actually cited, and you have a retrieval test built from real usage rather than from vibes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fac1t0y78w4ggignhpj9g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fac1t0y78w4ggignhpj9g.png" alt="Diagram: four retrieval stacks measured on the same golden session. Dense only scores recall@12 of 1.00, hit@4 of 0.00, MRR 0.12 in 4.4s. BM25 only scores 1.00, 1.00, 0.33 in 0.0s. Hybrid fused with RRF scores 1.00, 0.00, 0.17 in 0.5s. Hybrid plus a cross-encoder reranker scores 1.00, 1.00, 0.50 in 108.3s. recall@12 is 1.00 in every row, and the draft is handed all 12 passages anyway, so a better order inside those 12 buys nothing" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The reranker won on MRR and &lt;a href="mailto:hit@4"&gt;hit@4&lt;/a&gt;. It genuinely put better passages nearer the top.&lt;/p&gt;

&lt;p&gt;It also did not change recall@12 at all, and recall@12 is the only number with a consumer.&lt;/p&gt;

&lt;p&gt;The draft call gets all twelve passages in its prompt. It reads all twelve. There is no top-4 cutoff downstream, no truncation, nothing that treats passage 1 differently from passage 9.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsha2y0i6qv4qti6d9zvh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsha2y0i6qv4qti6d9zvh.png" alt="Obi-Wan meme: you were supposed to improve recall, you reordered 12 chunks nobody was ranking and added 108 seconds" width="360" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the reranker was spending 108 seconds improving an ordering that nothing downstream reads. It was optimising a metric we had accidentally chosen because it appears in every retrieval paper, not because our pipeline consumed it.&lt;/p&gt;

&lt;p&gt;Out it went, with the reasoning written into the top of &lt;code&gt;retriever.py&lt;/code&gt; so the next person does not "fix" its absence.&lt;/p&gt;

&lt;p&gt;And the honest caveat, which lives there too: this is one golden session. A harder query might genuinely need reranking to pull the right passage into the top twelve. The code is in git history, the eval is a make target, and when the golden set is fat enough to mean something we will run it again.&lt;/p&gt;

&lt;p&gt;Measure before you delete. Also measure before you keep.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two hours of embedding, four laptops
&lt;/h2&gt;

&lt;p&gt;3,639 chunks at roughly 1.8 seconds each on a shared 4 GB GPU is about two hours, which is about one hour and fifty minutes more than anyone wants to wait.&lt;/p&gt;

&lt;p&gt;But embedding is deterministic and the corpus splits cleanly by file. So it parallelises across people, not just across cores.&lt;br&gt;
&lt;/p&gt;
&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    C[71 files, 3,639 chunks] --&amp;gt; S[shard_files: disjoint quarters]
    S --&amp;gt; M[four laptops, --shard i/4]
    M --&amp;gt; R{sha256 AND chunk count match?}
    R -- yes --&amp;gt; SK[already embedded, skip]
    R -- no --&amp;gt; EM[embed, halve the batch on OOM]
    EM --&amp;gt; G[commit the shard, hand it back]
    SK --&amp;gt; G
    G --&amp;gt; I[rag/integrate.py]
    I --&amp;gt; V{same embedding model everywhere?}
    V -- no --&amp;gt; X[refuse: mixed vectors lie quietly]
    V -- yes --&amp;gt; O{every file in exactly one shard?}
    O -- in two --&amp;gt; X
    O -- in none --&amp;gt; W[warn, merge what arrived]
    O -- yes --&amp;gt; F[copy vectors into db/chroma]

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef start    fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
    classDef work     fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a
    classDef box      fill:#6ea8ff,stroke:#2f5fc4,color:#1a1a1a
    classDef good     fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef bad      fill:#ff9a5c,stroke:#c4602a,color:#1a1a1a

    class R,V,O decision
    class C start
    class M,EM work
    class S,G,I,SK box
    class F good
    class X,W bad&lt;/code&gt;&lt;/pre&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# on four different machines, one quarter each&lt;/span&gt;
uv run rag/ingest.py &lt;span class="nt"&gt;--shard&lt;/span&gt; 1/4 &lt;span class="nt"&gt;--out&lt;/span&gt; db/chroma-shards/1
uv run rag/ingest.py &lt;span class="nt"&gt;--shard&lt;/span&gt; 2/4 &lt;span class="nt"&gt;--out&lt;/span&gt; db/chroma-shards/2
&lt;span class="c"&gt;# ... then, once everyone commits their shard back&lt;/span&gt;
uv run rag/integrate.py db/chroma-shards/&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The merge does no embedding at all. It checks that every shard used the same embedding model, that no file landed in two shards, and that no file landed in none, then copies the vectors into one store.&lt;/p&gt;

&lt;p&gt;Those checks are not paranoia. Mixed embedding models do not crash. They return confidently wrong neighbours forever, which is a far worse failure than a stack trace.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F52zaej8meo8altwu3oox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F52zaej8meo8altwu3oox.png" alt="Oprah meme: you get a shard, and you get a shard, everybody gets a shard" width="360" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Building this also shook out a real bug in the incremental logic. Resume was deciding "already done" by comparing the file's sha256 against what was in the store.&lt;/p&gt;

&lt;p&gt;A run killed halfway through a file leaves that file's sha perfectly correct and its chunk count short, so it would have been marked done forever, silently missing half a book.&lt;/p&gt;

&lt;p&gt;The fix is one &lt;code&gt;AND&lt;/code&gt;: sha256 and chunk count both have to match.&lt;/p&gt;

&lt;p&gt;Then we committed the finished store. &lt;code&gt;db/chroma&lt;/code&gt; is 69 MB in git, which is nothing, and it means nobody else on the team ever embeds anything. Clone, run, search.&lt;/p&gt;
&lt;h2&gt;
  
  
  Now that retrieval is free, spend it on drafts
&lt;/h2&gt;

&lt;p&gt;The nice thing about killing your most expensive call is that the cheap calls get interesting.&lt;/p&gt;

&lt;p&gt;A draft is now a few tenths of a cent. So instead of drafting once and retrying on failure, the pipeline fires three or four drafts at once at temperature 0.7 and lets them race.&lt;/p&gt;

&lt;p&gt;Each reply gets resolved and checked in Go as it lands. Drafts that fail the checks are rejected on the spot. The first one that passes goes on to the review call. Only if all of them fail does the batch retry, with the closest draft's failures appended to the prompt.&lt;/p&gt;

&lt;p&gt;Every draft is kept and shown, including the rejected ones with the checks they failed, because "here are four attempts and why three of them were bad" is more useful to the person reading than one draft and a shrug.&lt;/p&gt;

&lt;p&gt;This did produce one genuinely dumb bug, which parallelism made much more likely.&lt;/p&gt;

&lt;p&gt;In one run the first batch produced a draft that passed every check. The reviewer then asked for a revision. Eight redrafts later, none of them passing, the pipeline shipped the closest failing redraft.&lt;/p&gt;

&lt;p&gt;It had a passing draft in hand and threw it away for a worse one. The fix is the obvious fallback: if no redraft passes, ship the draft that already did, with the reviewer's notes attached.&lt;/p&gt;

&lt;p&gt;Retries are cheap. Losing work you already paid for is not.&lt;/p&gt;
&lt;h2&gt;
  
  
  The check that replaced trusting the grounding metadata
&lt;/h2&gt;

&lt;p&gt;Handing the passages in ourselves unlocked the thing I actually care about.&lt;/p&gt;

&lt;p&gt;When a hosted search tool returns grounding metadata, you know which chunks the model looked at. You do not know that the words it put in quotation marks are in any of them.&lt;/p&gt;

&lt;p&gt;Now the pipeline can prove it.&lt;/p&gt;

&lt;p&gt;Every &lt;code&gt;history.sources[].passage&lt;/code&gt; in the output has to appear word for word in a chunk from the file it names. Same for an authority's &lt;code&gt;exact_words&lt;/code&gt; when it cites one of the given files. Markdown, line wrapping and quote style are normalised away first, and a &lt;code&gt;...&lt;/code&gt; marks an omission, with the remaining pieces required to appear in order.&lt;/p&gt;

&lt;p&gt;A failure is not a warning. It is a check failure, exactly like a length violation or an unresolved law citation, and it feeds the same retry loop that everything else does.&lt;/p&gt;

&lt;p&gt;This is the difference between "the model had access to the right book" and "the model quoted the right book correctly," and only one of those is worth showing a reviewer.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the bill looks like now
&lt;/h2&gt;

&lt;p&gt;One real run, end to end, with 15 model calls including 12 drafts across three batches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;210,976 input tokens, 55,285 output tokens&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$0.045&lt;/strong&gt;, about ₹4.3&lt;/li&gt;
&lt;li&gt;retrieval: 0.5 seconds of it, and none of the money&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Against $0.70 for the same pipeline shape on hosted search. Fifteen times cheaper, and the expensive part is now the part that does the actual writing, which is how it should be.&lt;/p&gt;

&lt;p&gt;Two honest footnotes on that number.&lt;/p&gt;

&lt;p&gt;Atlas reports most of each draft's input as cached, and we charge every input token at the full rate, so $0.045 is a ceiling, not an estimate.&lt;/p&gt;

&lt;p&gt;And latency went up, not down. Retrieval dropped from 117 seconds to half a second, but DeepSeek thinks hard before each draft, so a full run is still two to four minutes. &lt;/p&gt;

&lt;p&gt;We bought cost, not speed. For "paste a design doc, come back with a coffee," that is the right trade. For a chat box it would be the wrong one.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I would steal
&lt;/h2&gt;

&lt;p&gt;If your retrieval is a model call, you are paying reasoning prices for a lookup.&lt;/p&gt;

&lt;p&gt;Pull it onto your own machine, spend the effort on cleaning and chunking rather than on model selection, embed a contextual header you never show anyone, fuse dense and lexical on ranks instead of scores, and check the quotes rather than trusting them.&lt;/p&gt;

&lt;p&gt;Then build the eval before you build the clever part. Ours told us to throw the clever part away, which saved 108 seconds a search and a permanent dependency we did not need.&lt;/p&gt;

&lt;p&gt;The best component in this system is the one that is not in it.&lt;/p&gt;



&lt;p&gt;&lt;em&gt;&lt;br&gt;
Your team's attention is limited, and the deluge of AI-generated code is making it harder to keep production secure and reliable without slowing you down.&lt;/em&gt;&lt;/p&gt;
&lt;em&gt;

&lt;p&gt;I'm building &lt;strong&gt;LiveReview&lt;/strong&gt;, a blast-radius aware AI code review built for your business-critical systems.&lt;/p&gt;

&lt;p&gt;Instead of presenting every diff with equal emphasis, &lt;strong&gt;LiveReview scores each change by blast radius — how far its impact reaches through your call graph — so you can focus attention where it actually matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spend code review effort where business risk is highest — not spread evenly across every diff.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;
        LiveReview
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Blast-Radius Aware AI Code Review for Business-Critical Systems
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/png/logo-with-text.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fpng%2Flogo-with-text.png" alt="LiveReview" height="80"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml" rel="noopener noreferrer"&gt;&lt;img alt="mcp-testcases.yml" title="mcp-testcases.yml: MCP integration test suite" src="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml/badge.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LiveReview: Risk-Aware AI Code Review for Business-Critical Systems&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;LiveReview is an AI code reviewer that scores every hunk of a diff by &lt;strong&gt;blast radius&lt;/strong&gt;: how far a change reaches through your call graph, how much persistent state it touches, and how well-tested it is. A 3-line change to a shared auth check can outrank a 300-line UI tweak. Your team's attention goes to the highest-risk code first, not spread evenly across every diff.&lt;/p&gt;


  
    
    &lt;span class="m-1"&gt;blast-radius-demo.mp4&lt;/span&gt;
  

  

  


&lt;p&gt;&lt;i&gt;LiveReview's Blast Radius &amp;amp; Review Priority scoring, live in the diff viewer.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;The exact math, not a black box&lt;/th&gt;

&lt;th&gt;Visualize blast radius at a glance&lt;/th&gt;

&lt;th&gt;Every factor that feeds the score&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-3.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-3.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-4.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-4.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-2.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-2.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;&lt;/p&gt;

How does Blast Radius scoring work? (a more technical explanation)

&lt;p&gt;&lt;strong&gt;Here's the goal:&lt;/strong&gt;&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;A 3-line fix in a function used by 40 other files, that also writes to a database, should score high.&lt;/li&gt;

&lt;li&gt;A 300-line UI change in one file, fully covered by tests…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;b&gt;Click below to try LiveReview with your codebase:&lt;/b&gt;&lt;/p&gt;

&lt;/em&gt;&lt;p&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hexmos.com/livereview" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvls0pq7nymbrll98je6s.png" alt="LiveReview Banner" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rag</category>
      <category>ai</category>
      <category>go</category>
      <category>python</category>
    </item>
    <item>
      <title>Cheap RAG in Go with Gemini File Search: no vector DB, two calls, one hosted store</title>
      <dc:creator>Athreya aka Maneshwar</dc:creator>
      <pubDate>Tue, 22 Sep 2026 07:12:46 +0000</pubDate>
      <link>https://dev.to/lovestaco/cheap-rag-in-go-with-gemini-file-search-no-vector-db-two-calls-one-hosted-store-4kb5</link>
      <guid>https://dev.to/lovestaco/cheap-rag-in-go-with-gemini-file-search-no-vector-db-two-calls-one-hosted-store-4kb5</guid>
      <description>&lt;p&gt;&lt;em&gt;Hello, I'm Maneshwar, and I'm building LiveReview — a blast-radius aware AI code review built for your business-critical systems. &lt;a href="https://github.com/HexmosTech/LiveReview/" rel="noopener noreferrer"&gt;Star us&lt;/a&gt; to help devs discover the project, give it a try, and share your feedback to help improve the product.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every team I have been on has the same shelf.&lt;/p&gt;

&lt;p&gt;Nine engineering books somebody swears by, a couple of long essays that get linked in every third design review, and sixty-odd internal blog posts and postmortems that nobody rereads.&lt;/p&gt;

&lt;p&gt;About 1.4 million tokens of "we already learned this once."&lt;/p&gt;

&lt;p&gt;I wanted a tool where you paste a design proposal, an ADR, a postmortem draft, and it comes back with: here is what is actually being proposed, here is the pattern underneath it, and here are the three places on the shelf where we, or someone smarter, already ran into this exact shape.&lt;/p&gt;

&lt;p&gt;Grounded. With citations. Not "an LLM read your doc and had feelings about it."&lt;/p&gt;

&lt;p&gt;The obvious build is a vector database, an embedding pipeline, a chunker, a reranker, and a weekend.&lt;/p&gt;

&lt;p&gt;I built it with none of those, in Go, on Gemini's free tier, and the retrieval side of it costs nothing to run.&lt;/p&gt;

&lt;p&gt;This post is about how, and about the four things that bit me on the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAG without owning the R
&lt;/h2&gt;

&lt;p&gt;Gemini has a thing called &lt;a href="https://ai.google.dev/gemini-api/docs/file-search" rel="noopener noreferrer"&gt;File Search&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You create a "store", upload documents into it, and Google chunks them, embeds them, and indexes them.&lt;/p&gt;

&lt;p&gt;Then you attach that store to a normal &lt;code&gt;generateContent&lt;/code&gt; call as a tool, and the model searches it by itself, mid-answer, and hands you back the chunks it used as &lt;code&gt;groundingMetadata&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No pgvector. No Pinecone bill. No embedding model to pick and then regret.&lt;/p&gt;

&lt;p&gt;The pricing is the part that made me sit up.&lt;/p&gt;

&lt;p&gt;Storage is free. Query-time embeddings are free. You pay once at indexing time, at &lt;a href="https://ai.google.dev/gemini-api/docs/pricing" rel="noopener noreferrer"&gt;embedding prices&lt;/a&gt;, and then the chunks the model pulls in are billed as ordinary context tokens on the call you were already making.&lt;/p&gt;

&lt;p&gt;On the free tier the store caps at 1 GB. My entire shelf, every book and every post converted to markdown, is 5.3 MB.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh09pkzx4z6kx7dd8adud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh09pkzx4z6kx7dd8adud.png" alt="Diagram: the whole pipeline. Once: 71 markdown files go through a sha256 diff into an ingest step that uploads to a Gemini File Search store. Every question: a design proposal goes to call 1 which analyzes it without tools and emits a generalization plus retrieval queries, then call 2 drafts with the File Search tool attached and returns grounding chunks, then deterministic checks with up to two retries produce a review JSON with sources and cited laws" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the architecture is embarrassingly short.&lt;/p&gt;

&lt;p&gt;One Go binary. SQLite via &lt;code&gt;modernc.org/sqlite&lt;/code&gt;, so no cgo. A REST client for Gemini with no SDK, because I wanted to log the exact request and response bodies verbatim, and SDKs love to hide those.&lt;/p&gt;

&lt;p&gt;Two model calls per question. One hosted store. That is the whole thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step zero: getting books into markdown without losing the spaces
&lt;/h2&gt;

&lt;p&gt;Before any of the clever parts, the corpus has to exist as text, and this cost me an evening.&lt;/p&gt;

&lt;p&gt;The first tool I reached for was &lt;a href="https://github.com/microsoft/markitdown" rel="noopener noreferrer"&gt;markitdown&lt;/a&gt;. It gave me headings, sort of, and it also gave me this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Theubiquityoffrustrating,unhelpfulsoftwareinterfaceshasmotivateddecadesofresearch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every word in the PDF glued to its neighbour. The embedding model does not know what "Theubiquityoffrustrating" is, and neither does the retrieval.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pdftotext&lt;/code&gt; fixed the spacing and threw away every heading, so a 300-page book became one undifferentiated scroll.&lt;/p&gt;

&lt;p&gt;The one that won was &lt;a href="https://pymupdf.readthedocs.io/en/latest/pymupdf4llm/" rel="noopener noreferrer"&gt;pymupdf4llm&lt;/a&gt;. It looks at font sizes to decide what is a heading, keeps bold and italics, and its text extraction handled the spacing correctly on the same PDF.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run &lt;span class="nt"&gt;--with&lt;/span&gt; pymupdf4llm python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import pymupdf4llm, pathlib
pathlib.Path('out.md').write_text(pymupdf4llm.to_markdown('in.pdf'))
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Forty-four real headings out of one essay, versus zero.&lt;/p&gt;

&lt;p&gt;Headings matter more than they look like they should here, because File Search chunks on whitespace with a token budget. A chunk that starts at a heading is a chunk that means something on its own. A chunk that starts mid-sentence in a wall of text is noise with an embedding attached.&lt;/p&gt;

&lt;p&gt;I put the conversion behind &lt;code&gt;make process-data&lt;/code&gt; so nobody on the team has to rediscover this. EPUBs, PDFs, and a sync of our blog repos, all into one &lt;code&gt;post_processed_data/&lt;/code&gt; tree of markdown.&lt;/p&gt;
&lt;h2&gt;
  
  
  The store belongs to the key, not to you
&lt;/h2&gt;

&lt;p&gt;This is the first thing that bit me, and it is not in the big print.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3b15zaljtwr5tdnwwmte.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3b15zaljtwr5tdnwwmte.png" alt="Matrix Morpheus meme: what if I told you the File Search store belongs to the API key, not to your app" width="360" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A File Search store lives inside the Google Cloud project behind the API key that created it.&lt;/p&gt;

&lt;p&gt;Create a store with key A, try to search it with key B from a different project, and it does not error in a helpful way. It just is not there.&lt;/p&gt;

&lt;p&gt;That matters the moment you have more than one key, which on the free tier you will, because the per-minute caps are real and the fix everyone reaches for is "rotate across a few keys."&lt;/p&gt;

&lt;p&gt;You cannot rotate a tool-attached call across keys. The store pins you.&lt;/p&gt;

&lt;p&gt;So keys got roles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F37p4y7ze6v9klzeiam7o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F37p4y7ze6v9klzeiam7o.png" alt="Diagram: keys split by role. keys/store.txt has key 0 owning store 0 (primary) and key 1 owning store 1 (fallback), and call 2 tries store 0 with key 0 then falls to store 1 with key 1, never key 1 on store 0. keys/rotate.txt has keys 2 through 5 rotated on 429, 401, 403 and 5xx for call 1 and corpus index summaries which have no tool attached" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;keys/store.txt&lt;/code&gt; is the short list. Each key in it owns a complete copy of the corpus in its own store. Ingest uploads to every one of them. The search call tries store 0 with key 0, and only on a quota or auth failure falls through to store 1 with key 1.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;keys/rotate.txt&lt;/code&gt; is the long list, for any call with no tool attached: the analysis call, the corpus-index summaries. Those genuinely do not care which key answers, so they rotate on 429, 401, 403, and anything 5xx.&lt;/p&gt;

&lt;p&gt;Two things I got wrong before I got them right:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;503 is not a key problem.&lt;/strong&gt; Gemini returns 503 "high demand" when Google is busy, and switching keys does nothing except burn another key's quota. So the client waits and retries the same key a couple of times before falling over.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key goes in the &lt;code&gt;x-goog-api-key&lt;/code&gt; header, never the URL.&lt;/strong&gt; Put it in the query string and the first transport error prints your key into your own logs. Ask me how I know.&lt;/p&gt;

&lt;p&gt;Yes, two stores means the one-time indexing cost happens twice. It is the price of the search call never dying on a single quota, and at embedding prices for 5 MB, it is coffee money.&lt;/p&gt;
&lt;h2&gt;
  
  
  Ingest is a diff, not an upload
&lt;/h2&gt;

&lt;p&gt;The ingest step is where "just upload the folder" turns into an actual program.&lt;/p&gt;

&lt;p&gt;Every file gets a sha256. A manifest in SQLite records &lt;code&gt;path -&amp;gt; sha -&amp;gt; document id&lt;/code&gt; for each store. Then:&lt;br&gt;
&lt;/p&gt;
&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[walk data/**/*.md, sha256 each] --&amp;gt; B{path in manifest?}
    B -- no --&amp;gt; U[upload to store]
    B -- yes --&amp;gt; C{sha changed?}
    C -- no --&amp;gt; S[skip, unchanged]
    C -- yes --&amp;gt; D[delete old doc id] --&amp;gt; U
    U --&amp;gt; P[poll the indexing operation until done]
    P --&amp;gt; M[upsert manifest: path, sha, doc id]
    A --&amp;gt; R{manifest path missing on disk?}
    R -- yes --&amp;gt; X[delete doc from store, drop manifest row]
    R -- no --&amp;gt; S
    M --&amp;gt; I{set of path+sha changed?}
    S --&amp;gt; I
    X --&amp;gt; I
    I -- no --&amp;gt; K[corpus index unchanged]
    I -- yes --&amp;gt; G[summarize new files with flash-lite, one line each] --&amp;gt; J[store corpus_index]

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef start    fill:#e9ecef,stroke:#6c757d,color:#1a1a1a
    classDef net      fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef local    fill:#6ea8ff,stroke:#2f5fc4,color:#1a1a1a
    classDef idx      fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a

    class B,C,R,I decision
    class A start
    class U,D,P,X net
    class S,M,K local
    class G,J idx&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Unchanged files are skipped. Changed files have their old document deleted first, then get re-uploaded. Files that vanished from disk get deleted from the store.&lt;/p&gt;

&lt;p&gt;Each upload is a multipart POST that returns a long-running operation, and you poll it until &lt;code&gt;done&lt;/code&gt; before you trust the document id. Four uploads run in parallel. Serial, a 71-file corpus takes about ten minutes. Parallel, a few.&lt;/p&gt;

&lt;p&gt;The chunking is set per upload, and I landed on 400 tokens with 60 of overlap. The docs' example is 200 and 20, which for a book felt like reading through a letterbox.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"displayName"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"chunkingConfig"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"whiteSpaceConfig"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"maxTokensPerChunk"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"maxOverlapTokens"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;// multipart/related: part 1 is this JSON, part 2 is the markdown bytes&lt;/span&gt;
&lt;span class="c"&gt;// POST /upload/v1beta/{store}:uploadToFileSearchStore&lt;/span&gt;
&lt;span class="c"&gt;// with X-Goog-Upload-Protocol: multipart&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The last box in that flowchart is the corpus index, and it is small but it is the thing that makes the next section work.&lt;/p&gt;

&lt;p&gt;For every file, a one-sentence summary from the cheapest model available (&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;), keyed by the file's sha so it is only ever generated once. All the lines get joined into one block, "path, summary", that gets pasted into the analysis prompt.&lt;/p&gt;

&lt;p&gt;That way the model deciding what to search for knows what is actually on the shelf. It aims at sources that exist instead of guessing.&lt;/p&gt;
&lt;h2&gt;
  
  
  Search with the pattern, not the post
&lt;/h2&gt;

&lt;p&gt;This is the idea in the post that I would keep if I had to throw everything else away.&lt;/p&gt;

&lt;p&gt;Embedding search finds things that sound alike.&lt;/p&gt;

&lt;p&gt;Paste a proposal that says "we should standardize on one MCP transport across all our agents" straight into retrieval, and you get back every chunk that contains the words "MCP", "agents", and "standardize". Which is your own recent blog posts about MCP and agents.&lt;/p&gt;

&lt;p&gt;That is not precedent. That is a mirror.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3hdajakprhf4roek2qjq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3hdajakprhf4roek2qjq.png" alt="Diagram: the same proposal searched two ways. Raw text as the query returns blog posts about MCP and agents, chunks about the subject with no precedent. The generalization as the query, fragmented incompatible implementations to one open standard to mass adoption, returns the browser wars, Rickover on standardization, and Han Fei on uniform law, the same pattern in other clothes" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you actually want is the shape of the situation with the nouns removed.&lt;/p&gt;

&lt;p&gt;"Fragmented, incompatible implementations, then one open standard, then mass adoption."&lt;/p&gt;

&lt;p&gt;Search the shelf with that, and the browser wars come back. Rickover on standardizing the nuclear navy comes back. A 2,300-year-old Legalist essay on uniform law comes back.&lt;/p&gt;

&lt;p&gt;Same pattern, different clothes. That is what a reviewer with thirty years of reading brings, and it is what the model cannot do if you let it search with the raw text.&lt;/p&gt;

&lt;p&gt;So the pipeline never lets it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Call 1&lt;/strong&gt; gets the lawbook, the corpus index, and the proposal, with no tools attached. It returns JSON: the motive, what is actually happening, who the actors are, the generalization, and two or three retrieval queries derived from that generalization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Call 2&lt;/strong&gt; gets the analysis from call 1, the File Search tool pointed at the store, and the instruction to search with those queries and then draft.&lt;/p&gt;

&lt;p&gt;The raw document is in call 2's context, so the draft can quote it. But the search terms came from call 1, and they are about the pattern, not the subject.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"systemInstruction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"parts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"...the lawbook..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"parts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PROPOSAL:&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;\n\n&lt;/span&gt;&lt;span class="s2"&gt;ANALYSIS FROM CALL 1:&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;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Search the corpus with the retrieval queries above, then reply with the JSON contract."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fileSearch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fileSearchStoreNames"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"fileSearchStores/abc123"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"generationConfig"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"responseMimeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"temperature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The response carries &lt;code&gt;candidates[0].groundingMetadata.groundingChunks&lt;/code&gt;, each with the file's display name and the retrieved text. Those get shown to the user, in full, next to the draft. If the "authority" the review leans on is not in that list, it did not come from the shelf, and the checks catch that.&lt;/p&gt;
&lt;h2&gt;
  
  
  Every instruction is a law, every reply cites its laws
&lt;/h2&gt;

&lt;p&gt;There is no free-text system prompt anywhere in this thing.&lt;/p&gt;

&lt;p&gt;The prompts are an &lt;a href="https://github.com/shrsv/AgentLaws" rel="noopener noreferrer"&gt;AgentLaws&lt;/a&gt; lawbook: a folder of markdown where every instruction the model sees is a numbered law, grouped into chapters, versioned in git, and compiled once at startup.&lt;/p&gt;

&lt;p&gt;The model is required to return &lt;code&gt;applied_laws&lt;/code&gt;, the numbers it relied on, and the pipeline resolves each one back to &lt;code&gt;file:line&lt;/code&gt; in the lawbook.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxi82fvjo5fhuqcqfr1xi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxi82fvjo5fhuqcqfr1xi.png" alt="Star Wars checkpoint officer meme: applied_laws 2.4.1 resolves to analysis/generalization.md:14, an older law, sir, but it resolves" width="360" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A number that does not resolve fails the run. Which sounds pedantic until the first time the model confidently cites law 7.2.3 in a lawbook with six chapters.&lt;/p&gt;

&lt;p&gt;The practical win is that "why did it do that" has an answer that is a file and a line number, and "make it stop doing that" is a pull request against a markdown file, not archaeology in a Go string.&lt;/p&gt;

&lt;p&gt;The output contract is the last thing in the prompt, because that is where the model weights it most. Laws about tone and structure go first, the JSON schema goes last.&lt;/p&gt;
&lt;h2&gt;
  
  
  Trust, but run the checks
&lt;/h2&gt;

&lt;p&gt;The model also fills a &lt;code&gt;checks&lt;/code&gt; block in its own reply, a self-audit. I do not trust it, but it is useful as a second signal.&lt;/p&gt;

&lt;p&gt;Before that gets read, a small pile of deterministic checks in plain Go runs on every draft: length bounds, no markdown, at most one question, no "see point N" references, every cited law resolved.&lt;br&gt;
&lt;/p&gt;
&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TD
    A[call 2: search + draft, JSON mode] --&amp;gt; B{reply parses as JSON?}
    B -- no --&amp;gt; F[resend once without JSON mode, extract first object] --&amp;gt; C
    B -- yes --&amp;gt; C[resolve every cited law number to file:line]
    C --&amp;gt; D{all citations resolve?}
    D -- no --&amp;gt; R
    D -- yes --&amp;gt; E[deterministic checks: length, no markdown, one question max, distinct openers]
    E --&amp;gt; G{checks pass?}
    G -- no --&amp;gt; R{attempts left?}
    R -- yes --&amp;gt; H[append the failures to the user message] --&amp;gt; A
    R -- no --&amp;gt; S[ship the draft with failures listed]
    G -- yes --&amp;gt; V[call 3: reviewer pass, verdict + objections]
    V --&amp;gt; W{verdict?}
    W -- ship --&amp;gt; O[return JSON with sources and applied laws]
    W -- revise, rounds left --&amp;gt; H
    W -- revise, no rounds --&amp;gt; S

    classDef decision fill:#f4d35e,stroke:#b8991f,color:#1a1a1a
    classDef llm      fill:#5ee6c8,stroke:#1f9c86,color:#1a1a1a
    classDef local    fill:#6ea8ff,stroke:#2f5fc4,color:#1a1a1a
    classDef bad      fill:#ff9a5c,stroke:#c4602a,color:#1a1a1a
    classDef good     fill:#9d8cff,stroke:#5b4bcc,color:#1a1a1a

    class B,D,G,R,W decision
    class A,F,V llm
    class C,E,H local
    class S bad
    class O good&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;A failure gets appended to the user message as a plain list, "the previous attempt failed these checks, fix them", and call 2 runs again. Two retries, then the draft ships anyway with the failures listed, because a draft with a warning is more useful than a spinner that gave up.&lt;/p&gt;

&lt;p&gt;One box in there deserves its own sentence: &lt;strong&gt;JSON mode plus a tool is not guaranteed&lt;/strong&gt;. &lt;code&gt;responseMimeType: application/json&lt;/code&gt; together with File Search worked most of the time and then, occasionally, returned prose with a JSON object somewhere in it. The fix is boring: if the reply does not parse, resend once without JSON mode and pull the first &lt;code&gt;{...}&lt;/code&gt; out of the text. Both attempts get logged.&lt;/p&gt;
&lt;h2&gt;
  
  
  The bug that made me split the database
&lt;/h2&gt;

&lt;p&gt;The tool is used by five people, and each of them runs it against their own SQLite file, so history is per person.&lt;/p&gt;

&lt;p&gt;The first version put everything in that one file: sessions, steps, and the ingest manifest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9sk2s29l1a6ynv6wjawn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9sk2s29l1a6ynv6wjawn.png" alt="Bad Luck Brian meme: creates a fresh SQLite DB for a teammate, re-uploads all 71 files and duplicates every doc in the store" width="360" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Spot it?&lt;/p&gt;

&lt;p&gt;A new teammate makes a new database. New database, empty manifest. Empty manifest means every file on disk is "new", so all 71 get uploaded again. And because the manifest is empty, ingest does not know the old document ids, so nothing gets deleted.&lt;/p&gt;

&lt;p&gt;The store now has two of everything. Do it five times and search quality quietly rots, because every query returns the same chunk five times and crowds out the second-best hit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqlxpga3o08jpzyp59dan.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqlxpga3o08jpzyp59dan.png" alt="Diagram: three per-person session databases, alice.db, bob.db, you.db, each holding sessions and steps, all reading from one shared corpus.db that holds the store names per key, the manifest of path to sha256 to doc id, and the corpus index. corpus.db mirrors the Google File Search store of 71 documents chunked at 400 tokens with 60 overlap. The bug this fixed: manifest lived in the session db, so a new teammate got an empty manifest, re-uploaded all 71 files, deleted nothing, and the store filled with duplicates" width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fix is one sentence: the manifest is a property of the store, not of whoever is asking questions today.&lt;/p&gt;

&lt;p&gt;So there are two databases now. &lt;code&gt;corpus.db&lt;/code&gt; holds the store names, the manifest, and the corpus index, and every session database reads from it. &lt;code&gt;alice.db&lt;/code&gt; holds Alice's sessions and nothing else.&lt;/p&gt;

&lt;p&gt;Delete &lt;code&gt;corpus.db&lt;/code&gt; and you get a full re-ingest. Delete &lt;code&gt;alice.db&lt;/code&gt; and nothing about the corpus changes.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the free tier actually gives you
&lt;/h2&gt;

&lt;p&gt;Since "cheap" is in the title, the honest numbers, as of when I built this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Models.&lt;/strong&gt; Flash only. &lt;code&gt;gemini-3.5-flash&lt;/code&gt; for the real calls, &lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt; for the throwaway summaries. Pro returns a 429 with &lt;code&gt;limit: 0&lt;/code&gt; on free keys. A paid key unlocks it with one env var, and I have not needed to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limits.&lt;/strong&gt; Per-minute caps are the thing you hit, not per-day. Ingest backs off with a growing wait when every key is rate limited, and the UI shows each key fallover as it happens instead of hiding it behind a spinner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storage.&lt;/strong&gt; 1 GB per store. My 5.3 MB corpus does not register.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost per question.&lt;/strong&gt; Two Flash calls, sometimes three with the review pass, around 10k tokens in and 2k out. The retrieved chunks are already counted in that. On the free tier, zero. On a paid key, well under a cent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency.&lt;/strong&gt; 30 to 90 seconds end to end, most of it call 2 doing the search and the draft. That is fine for "paste a design doc, get a review" and would be wrong for a chat box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The catch.&lt;/strong&gt; On the free tier, Google may train on what you upload. My shelf is published books and public blog posts, so I do not care. If your corpus is your customer contracts, read &lt;a href="https://ai.google.dev/gemini-api/terms" rel="noopener noreferrer"&gt;the terms&lt;/a&gt; before you &lt;code&gt;make ingest&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I would tell you to steal
&lt;/h2&gt;

&lt;p&gt;If you have a shelf, and a question shaped like "have we seen this before", you do not need a vector database to answer it.&lt;/p&gt;

&lt;p&gt;You need markdown with real headings, a hosted store you diff against instead of re-uploading, keys with roles because the store pins you to one, and a first model call whose only job is to turn the question into the pattern underneath it.&lt;/p&gt;

&lt;p&gt;The rest is checks and logging, and the checks are the part you will thank yourself for.&lt;/p&gt;



&lt;p&gt;&lt;em&gt;&lt;br&gt;
Your team's attention is limited, and the deluge of AI-generated code is making it harder to keep production secure and reliable without slowing you down.&lt;/em&gt;&lt;/p&gt;
&lt;em&gt;

&lt;p&gt;I'm building &lt;strong&gt;LiveReview&lt;/strong&gt;, a blast-radius aware AI code review built for your business-critical systems.&lt;/p&gt;

&lt;p&gt;Instead of presenting every diff with equal emphasis, &lt;strong&gt;LiveReview scores each change by blast radius — how far its impact reaches through your call graph — so you can focus attention where it actually matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spend code review effort where business risk is highest — not spread evenly across every diff.&lt;/p&gt;

&lt;p&gt;⭐ Star it on GitHub:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/HexmosTech" rel="noopener noreferrer"&gt;
        HexmosTech
      &lt;/a&gt; / &lt;a href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;
        LiveReview
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Blast-Radius Aware AI Code Review for Business-Critical Systems
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/png/logo-with-text.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fpng%2Flogo-with-text.png" alt="LiveReview" height="80"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml" rel="noopener noreferrer"&gt;&lt;img alt="gitleaks.yml" title="gitleaks.yml: Secret scanning workflow" src="https://github.com/HexmosTech/LiveReview/actions/workflows/gitleaks.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml" rel="noopener noreferrer"&gt;&lt;img alt="osv-scanner.yml" title="osv-scanner.yml: Dependency vulnerability scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/osv-scanner.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml" rel="noopener noreferrer"&gt;&lt;img alt="govulncheck.yml" title="govulncheck.yml: Go vulnerability check" src="https://github.com/HexmosTech/LiveReview/actions/workflows/govulncheck.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml" rel="noopener noreferrer"&gt;&lt;img alt="semgrep.yml" title="semgrep.yml: Static analysis security scan" src="https://github.com/HexmosTech/LiveReview/actions/workflows/semgrep.yml/badge.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/gfx/dependabot-enabled.svg"&gt;&lt;img alt="dependabot-enabled" title="dependabot-enabled: Automated dependency updates are enabled" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fgfx%2Fdependabot-enabled.svg"&gt;&lt;/a&gt;&amp;nbsp;&lt;a href="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml" rel="noopener noreferrer"&gt;&lt;img alt="mcp-testcases.yml" title="mcp-testcases.yml: MCP integration test suite" src="https://github.com/HexmosTech/LiveReview/actions/workflows/mcp-testcases.yml/badge.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LiveReview: Risk-Aware AI Code Review for Business-Critical Systems&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;LiveReview is an AI code reviewer that scores every hunk of a diff by &lt;strong&gt;blast radius&lt;/strong&gt;: how far a change reaches through your call graph, how much persistent state it touches, and how well-tested it is. A 3-line change to a shared auth check can outrank a 300-line UI tweak. Your team's attention goes to the highest-risk code first, not spread evenly across every diff.&lt;/p&gt;


  
    
    &lt;span class="m-1"&gt;blast-radius-demo.mp4&lt;/span&gt;
  

  

  


&lt;p&gt;&lt;i&gt;LiveReview's Blast Radius &amp;amp; Review Priority scoring, live in the diff viewer.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;The exact math, not a black box&lt;/th&gt;

&lt;th&gt;Visualize blast radius at a glance&lt;/th&gt;

&lt;th&gt;Every factor that feeds the score&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-3.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-3.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-4.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-4.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;td&gt;&lt;a rel="noopener noreferrer" href="https://github.com/HexmosTech/LiveReview/./assets/screenshots/blast-radius/new-risk-score-2.webp"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2FHexmosTech%2FLiveReview%2FHEAD%2F.%2Fassets%2Fscreenshots%2Fblast-radius%2Fnew-risk-score-2.webp" width="280"&gt;&lt;/a&gt;&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;&lt;/p&gt;

How does Blast Radius scoring work? (a more technical explanation)

&lt;p&gt;&lt;strong&gt;Here's the goal:&lt;/strong&gt;&lt;/p&gt;


&lt;ul&gt;

&lt;li&gt;A 3-line fix in a function used by 40 other files, that also writes to a database, should score high.&lt;/li&gt;

&lt;li&gt;A 300-line UI change in one file, fully covered by tests…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/HexmosTech/LiveReview" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;b&gt;Click below to try LiveReview with your codebase:&lt;/b&gt;&lt;/p&gt;

&lt;/em&gt;&lt;p&gt;&lt;em&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hexmos.com/livereview" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvls0pq7nymbrll98je6s.png" alt="LiveReview Banner" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>ai</category>
      <category>rag</category>
      <category>gemini</category>
    </item>
  </channel>
</rss>
