<?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: Yash Kumar Saini</title>
    <description>The latest articles on DEV Community by Yash Kumar Saini (@yashksaini).</description>
    <link>https://dev.to/yashksaini</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%2F1242333%2F27bdd497-8c93-4290-a901-94eec3759056.jpeg</url>
      <title>DEV Community: Yash Kumar Saini</title>
      <link>https://dev.to/yashksaini</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yashksaini"/>
    <language>en</language>
    <item>
      <title>What I learned building wfreq</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Wed, 28 Oct 2026 15:10:42 +0000</pubDate>
      <link>https://dev.to/yashksaini/what-i-learned-building-wfreq-52f5</link>
      <guid>https://dev.to/yashksaini/what-i-learned-building-wfreq-52f5</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;How a word-frequency tool taught me to wrap errors instead of replacing them: from .expect() to thiserror, #[source] and per-file warnings.&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%2F39we89uqyk94wx19vg1h.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%2F39we89uqyk94wx19vg1h.gif" alt="wfreq running in the terminal" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built &lt;code&gt;wfreq&lt;/code&gt;, a small Rust CLI that prints the most common words in &lt;code&gt;.txt&lt;/code&gt;, &lt;code&gt;.md&lt;/code&gt;, &lt;code&gt;.pdf&lt;/code&gt; and &lt;code&gt;.docx&lt;/code&gt; files. The counting took an afternoon. The error messages took the rest of the project.&lt;/p&gt;

&lt;p&gt;This post is about the hardest part: making the tool tell the truth when something goes wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 1: &lt;code&gt;.expect()&lt;/code&gt; and a crash
&lt;/h2&gt;

&lt;p&gt;The first &lt;code&gt;main&lt;/code&gt; read the file like this:&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;let&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read_to_string&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;args&lt;/span&gt;&lt;span class="py"&gt;.file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"File can not read"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I pointed it at a PDF and got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;thread 'main' panicked at src/main.rs:30:57:
File can not read: Error { kind: InvalidData, message: "stream did not contain valid UTF-8" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two problems. A panic is a crash with a backtrace hint, not a message for a user. And &lt;code&gt;read_to_string&lt;/code&gt; only accepts valid UTF-8, so every PDF "can not be read", even though the file is right there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 2: &lt;code&gt;?&lt;/code&gt; and an error message nobody asked for
&lt;/h2&gt;

&lt;p&gt;The textbook fix is to return &lt;code&gt;Result&lt;/code&gt; from &lt;code&gt;main&lt;/code&gt; and use &lt;code&gt;?&lt;/code&gt;. I added a custom error enum with &lt;a href="https://crates.io/crates/thiserror" rel="noopener noreferrer"&gt;thiserror&lt;/a&gt; and nice messages:&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="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Error)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;CliError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[error(&lt;/span&gt;&lt;span class="s"&gt;"File not found or cannot read: {0}"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
    &lt;span class="nf"&gt;FileReadError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PathBuf&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&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;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;extract_text&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;args&lt;/span&gt;&lt;span class="py"&gt;.file&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="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I ran it on a missing file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;wfreq nope.txt
&lt;span class="go"&gt;Error: FileReadError("nope.txt")
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My message was nowhere. When &lt;code&gt;main&lt;/code&gt; returns &lt;code&gt;Err&lt;/code&gt;, Rust prints the error with &lt;strong&gt;&lt;code&gt;Debug&lt;/code&gt;&lt;/strong&gt;, not &lt;code&gt;Display&lt;/code&gt;, so all the &lt;code&gt;#[error("...")]&lt;/code&gt; text is ignored.&lt;/p&gt;

&lt;p&gt;The fix is small: keep &lt;code&gt;?&lt;/code&gt; everywhere, but handle the error once, at the top.&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;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error: {e}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// Display: the #[error] text&lt;/span&gt;
        &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;process&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;dyn&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// all the real work, using ? freely&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;exit(1)&lt;/code&gt; lives in &lt;code&gt;main&lt;/code&gt;, after &lt;code&gt;run()&lt;/code&gt; has returned and dropped everything, so nothing is skipped. And &lt;code&gt;eprintln!&lt;/code&gt; sends the message to stderr, so &lt;code&gt;wfreq notes.txt &amp;gt; top.txt&lt;/code&gt; never puts an error into the output file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 3: the lie
&lt;/h2&gt;

&lt;p&gt;Now errors looked nice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;wfreq nope.txt
&lt;span class="go"&gt;error: File not found or cannot read: nope.txt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I tried a file that &lt;strong&gt;did&lt;/strong&gt; exist but was not valid UTF-8. Same message: "File not found or cannot read". The file was right there. The real reason, invalid UTF-8, was gone.&lt;/p&gt;

&lt;p&gt;The culprit was one character in the conversion:&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="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.map_err&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="nn"&gt;CliError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;FileReadError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="nf"&gt;.to_path_buf&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;|_|&lt;/code&gt; throws the &lt;code&gt;io::Error&lt;/code&gt; away. "Not found", "permission denied", "is a directory" and "not UTF-8" all became the same vague sentence.&lt;/p&gt;

&lt;p&gt;The rule I took from this: &lt;strong&gt;when you convert an error, wrap it, don't replace it.&lt;/strong&gt; Your error adds the context (which file), and the original error keeps the cause (why).&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="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Error)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;CliError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[error(&lt;/span&gt;&lt;span class="s"&gt;"cannot read {}: {source}"&lt;/span&gt;&lt;span class="nd"&gt;,&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;path&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nd"&gt;display())]&lt;/span&gt;
    &lt;span class="n"&gt;FileRead&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PathBuf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nd"&gt;#[source]&lt;/span&gt;
        &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;read_to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;CliError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;FileRead&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="nf"&gt;.to_path_buf&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;source&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;Two details worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PathBuf&lt;/code&gt; has no &lt;code&gt;Display&lt;/code&gt;, because a path is not guaranteed to be valid text. So the message uses &lt;code&gt;{}&lt;/code&gt; with &lt;code&gt;.path.display()&lt;/code&gt; as an extra argument. The leading &lt;code&gt;.&lt;/code&gt; refers to the variant's field.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;#[source]&lt;/code&gt; makes thiserror implement &lt;code&gt;Error::source()&lt;/code&gt;, so tools that print error chains can follow it to the &lt;code&gt;io::Error&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the same file says what is actually wrong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: cannot read bad.txt: stream did not contain valid UTF-8
error: cannot read nope.txt: No such file or directory (os error 2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Version 4: the bug that compiled
&lt;/h2&gt;

&lt;p&gt;When I added PDF and DOCX support, the parser picked a function by file extension. One version looked like this:&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;match&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"txt"&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"md"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Parser success"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;"docx"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;docx&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&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 type-checks (once a missing &lt;code&gt;use std::fs;&lt;/code&gt; was added). Every arm returns &lt;code&gt;Result&amp;lt;String, CliError&amp;gt;&lt;/code&gt;. But the &lt;code&gt;;&lt;/code&gt; after &lt;code&gt;txt::parse(file_path)&lt;/code&gt; throws away the parsed text &lt;strong&gt;and&lt;/strong&gt; any error. Every &lt;code&gt;.txt&lt;/code&gt; file now "contained" the two words &lt;code&gt;parser&lt;/code&gt; and &lt;code&gt;success&lt;/code&gt;, and a broken file reported success.&lt;/p&gt;

&lt;p&gt;The compiler can't catch this: the types are right, the values are wrong. A test can:&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="nd"&gt;#[test]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;reads_text_from_stdin&lt;/span&gt;&lt;span class="p"&gt;()&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;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_with_stdin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&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="s"&gt;"-n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"to be or not to be"&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;stdout&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;out&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"Path: -&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt; 1. be              2&lt;/span&gt;&lt;span class="se"&gt;\n&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each arm should just be the parser call, so its result (text or error) is returned as-is:&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="s"&gt;"txt"&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"md"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;txt&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="s"&gt;"pdf"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="s"&gt;"docx"&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;docx&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Version 5: one bad file in a folder
&lt;/h2&gt;

&lt;p&gt;Then the tool learned to read folders. A folder can hold a broken PDF next to twenty good text files. Should one bad file stop the whole run?&lt;/p&gt;

&lt;p&gt;I decided:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One file given:&lt;/strong&gt; any problem is an error, exit code 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Several files or a folder:&lt;/strong&gt; a bad file is a warning on stderr and is skipped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every file failed:&lt;/strong&gt; that is an error too, because there is nothing to count.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;extract_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;merge_counts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;count_words&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;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="py"&gt;.case_sensitive&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;files&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;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nd"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"warning: {e} (skipped)"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="nf"&gt;.into&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;This only works because of version 3. The warning is useful because the error already carries the file and the reason:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;warning: cannot parse docs/old.pdf: PDF error: couldn't parse input: invalid file header (skipped)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without the path in the error, that line would say "cannot parse: invalid file header", and you would have to guess which of the twenty files it means.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell myself at the start
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;.expect()&lt;/code&gt; is for "this cannot happen", not for user input.&lt;/li&gt;
&lt;li&gt;Returning &lt;code&gt;Result&lt;/code&gt; from &lt;code&gt;main&lt;/code&gt; prints &lt;code&gt;Debug&lt;/code&gt;. Use a &lt;code&gt;run()&lt;/code&gt; function and print the error yourself.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;map_err(|_| ...)&lt;/code&gt; deletes information you can't get back. Wrap the error with &lt;code&gt;#[source]&lt;/code&gt; instead.&lt;/li&gt;
&lt;li&gt;Put the file path in the error, not in the caller. You will need it as soon as there is more than one file.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;match&lt;/code&gt; arm with a &lt;code&gt;;&lt;/code&gt; and a hard-coded &lt;code&gt;Ok(...)&lt;/code&gt; compiles fine. Write an end-to-end test that checks real output.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The integration tests in &lt;code&gt;tests/integration.rs&lt;/code&gt; check every error message mentioned here, so none of these bugs can come back quietly.&lt;/p&gt;

&lt;p&gt;full crash → crates.io story: &lt;a href="https://medium.com/@yash_k_saini/from-a-crash-to-crates-io-shipping-a-rust-cli-tool-f6ed59c16a9f" rel="noopener noreferrer"&gt;https://medium.com/@yash_k_saini/from-a-crash-to-crates-io-shipping-a-rust-cli-tool-f6ed59c16a9f&lt;/a&gt;&lt;br&gt;
repo: &lt;a href="https://github.com/yashksaini-coder/wfreq" rel="noopener noreferrer"&gt;https://github.com/yashksaini-coder/wfreq&lt;/a&gt;&lt;br&gt;
crate: &lt;a href="https://crates.io/crates/wfreq" rel="noopener noreferrer"&gt;https://crates.io/crates/wfreq&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>cli</category>
      <category>buildinpublic</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Dev log #27 Deep in the review queue: Hardening libp2p across Python and .NET</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Tue, 27 Oct 2026 13:08:58 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-deep-in-the-review-queue-hardening-libp2p-across-python-and-net-357l</link>
      <guid>https://dev.to/yashksaini/dev-log-deep-in-the-review-queue-hardening-libp2p-across-python-and-net-357l</guid>
      <description>&lt;p&gt;Hi, glad you found your way here. I'm Yash, a developer who contributes to open source, mostly p2p networking and AI tooling. Each week I write up what I shipped, what broke, and what fixing it taught me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Sometimes the most productive weeks aren't the ones where I'm smashing out hundreds of commits, but the ones where I'm acting as the glue for the projects I care about. This week was exactly that—a heavy focus on the libp2p ecosystem. I spent most of my energy in the review queue and hardening the Python implementation's connection logic. With 12 code reviews under my belt and a critical fix for dial timeouts in &lt;code&gt;py-libp2p&lt;/code&gt;, it was a week about stability and community growth rather than just raw feature velocity.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT I BUILT
&lt;/h2&gt;

&lt;p&gt;My hands-on coding time was almost exclusively dedicated to &lt;strong&gt;py-libp2p&lt;/strong&gt;. Networking code is notoriously finicky, and I’ve been digging into how we handle connection lifecycles lately.&lt;/p&gt;

&lt;p&gt;I spent a good chunk of time in the swarm logic. The standout task was addressing a bug where the swarm wasn't respecting the dial timeouts we'd configured. I pushed a commit that added 73 lines and stripped out 10, focusing on making sure every transport dial is strictly bounded by &lt;code&gt;ConnectionConfig.dial_timeout&lt;/code&gt;. It’s one of those "silent" bugs—everything looks fine until a peer is unresponsive, and suddenly your resource usage spikes because connections are hanging in limbo. Getting this right makes the whole library much more resilient in production environments.&lt;/p&gt;

&lt;p&gt;I also did some maintenance work on the interop side. We’ve been seeing some friction between the Nim and Python implementations, specifically around the Random Number Generator (Rng) API. I spent some time trying to unbreak the Nim interop build by pinning versions and aligning with the current &lt;code&gt;nim-libp2p&lt;/code&gt; API. Even though that specific PR didn't land (it ended up being closed), the process of debugging the interop failure was invaluable for understanding where our cross-language assumptions are breaking down.&lt;/p&gt;

&lt;h2&gt;
  
  
  PULL REQUESTS
&lt;/h2&gt;

&lt;p&gt;I had three PRs on the go this week, each hitting a different part of the stack.&lt;/p&gt;

&lt;p&gt;First up, in the &lt;strong&gt;py-libp2p&lt;/strong&gt; repo, I opened &lt;a href="https://github.com/libp2p/py-libp2p/pull/1568" rel="noopener noreferrer"&gt;fix(swarm): bound each transport dial by ConnectionConfig.dial_timeout&lt;/a&gt;. This is the core fix I mentioned earlier. It’s currently open and undergoing testing to ensure we don't accidentally drop valid connections under heavy load.&lt;/p&gt;

&lt;p&gt;I also opened a PR in &lt;strong&gt;agentmesh&lt;/strong&gt;: &lt;a href="https://github.com/google/agentmesh/pull/803" rel="noopener noreferrer"&gt;sdk/python: say which DNS addresses py-libp2p cannot dial&lt;/a&gt;. This is more of a DX (Developer Experience) improvement. There’s nothing more frustrating than a "Failed to dial" error that doesn't tell you &lt;em&gt;why&lt;/em&gt;. This PR adds better error reporting so users know exactly which DNS addresses are causing issues.&lt;/p&gt;

&lt;p&gt;Lastly, I had &lt;a href="https://github.com/libp2p/py-libp2p/pull/1566" rel="noopener noreferrer"&gt;fix(interop): unbreak Nim interop build&lt;/a&gt;. As I mentioned, this one was closed without a merge. We decided to take a different approach to the version pinning, but the investigation I did here helped us identify the root cause of the Rng API mismatch. Sometimes a closed PR is just as useful as a merged one for moving the conversation forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  ISSUES &amp;amp; DISCUSSIONS
&lt;/h2&gt;

&lt;p&gt;On the issue front, I formally documented the timeout bug I found by opening &lt;a href="https://github.com/libp2p/py-libp2p/issues/1567" rel="noopener noreferrer"&gt;host.connect ignores ConnectionConfig.dial_timeout&lt;/a&gt;. I like to open an issue even if I'm planning to fix it immediately—it provides a paper trail for why the code changed, which is a lifesaver for future maintainers (or future me in six months).&lt;/p&gt;

&lt;p&gt;I also got involved in a discussion over at the &lt;strong&gt;afara&lt;/strong&gt; project. I shared &lt;a href="https://github.com/tojunetwork/afara/discussions/20" rel="noopener noreferrer"&gt;notes from running afara end-to-end locally&lt;/a&gt; after doing a fresh clone and a pass over the toju.network. There’s a specific kind of "new user" perspective you only have once, and I try to capture that whenever I’m exploring a new part of the ecosystem. I’m still waiting for some feedback on those notes, but it’s a great way to help a project improve its onboarding flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  CODE REVIEWS
&lt;/h2&gt;

&lt;p&gt;This is where I spent the bulk of my "brain cycles" this week. I performed 12 reviews in total, which is a high ratio compared to my single commit. &lt;/p&gt;

&lt;p&gt;The vast majority of my review work was for &lt;strong&gt;dotnet-libp2p&lt;/strong&gt;. The NethermindEth team is doing some incredible work bringing libp2p to the .NET world, and I’ve been helping them audit their PRs. I approved 11 PRs there, covering a wide range of critical networking features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Stream Multiplexing&lt;/strong&gt;: I approved the addition of Mplex support and improvements to Yamux, including sizing frames to fit secure channel records and handling aborted streams on session loss.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Gossipsub &amp;amp; Routing&lt;/strong&gt;: Reviewed a change to compute gossip message IDs outside the router lock, which is a huge win for performance under high load. I also looked at how they recheck graylisting after RPC penalties.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Resource Management&lt;/strong&gt;: Approved an opt-in inbound stream limit, which is essential for protecting nodes against DoS attacks.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Session Handling&lt;/strong&gt;: Looked at PRs that prevent sessions from surviving peer disposal and keep routine initialization failures at the "Debug" log level to avoid spamming production logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Outside of the .NET world, I also left a comment on an &lt;strong&gt;agentmesh&lt;/strong&gt; PR regarding Python 3.14 support. We're trying to figure out the best way to handle the transition and whether to prefer 3.13 over 3.11 in certain environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  TECH STACK
&lt;/h2&gt;

&lt;p&gt;This week was 100% Python from a coding perspective, but my "conceptual" stack included C# and Nim as well. &lt;/p&gt;

&lt;p&gt;The additions-to-deletions ratio (+79/-12) reflects the fact that I was mostly adding safety checks and better error handling. It wasn't a "rip and replace" refactoring week; it was a "harden and protect" week. When you're working on a networking stack, you're often adding logic to handle edge cases that you didn't think of in the initial implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  WHAT'S NEXT
&lt;/h2&gt;

&lt;p&gt;Next week, I’m hoping to see those &lt;code&gt;py-libp2p&lt;/code&gt; PRs land. The dial timeout fix is a priority for me because it affects the stability of any project using the library for long-running nodes. I'll also be keeping an eye on the &lt;strong&gt;afara&lt;/strong&gt; discussion to see if my feedback helps them streamline their local setup.&lt;/p&gt;

&lt;p&gt;I might also dive back into the Nim interop issues. Now that we know why the Rng API was causing trouble, I want to make sure we have a robust way to prevent that kind of regression in the future. Onward!&lt;/p&gt;

</description>
      <category>networking</category>
      <category>discuss</category>
      <category>dotnet</category>
      <category>python</category>
    </item>
    <item>
      <title>Dev log #26 Deep in the libp2p weeds: Fixing DHT prefixes and Yamux resets</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Tue, 20 Oct 2026 04:12:05 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-26-deep-in-the-libp2p-weeds-fixing-dht-prefixes-and-yamux-resets-315n</link>
      <guid>https://dev.to/yashksaini/dev-log-26-deep-in-the-libp2p-weeds-fixing-dht-prefixes-and-yamux-resets-315n</guid>
      <description>&lt;p&gt;Hi, glad you found your way here. I'm Yash, a developer who contributes to open source, mostly p2p networking and AI tooling. Each week I write up what I shipped, what broke, and what fixing it taught me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I spent the week chasing ghosts in the networking stack, specifically focusing on the Python libp2p implementation. It was one of those weeks where you don't necessarily ship a brand-new feature, but you find the cracks in the foundation and start patching them up. I opened two significant PRs to fix protocol-level bugs in Kademlia and Yamux, and flagged four issues that were either blocking my workflow or breaking CI for everyone else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests
&lt;/h2&gt;

&lt;p&gt;Most of my energy this week went into &lt;code&gt;py-libp2p&lt;/code&gt;. If you've ever worked on p2p protocols, you know that the "happy path" is easy, but the edge cases are where the real work happens.&lt;/p&gt;

&lt;p&gt;I opened a PR titled &lt;a href="https://github.com/libp2p/py-libp2p/pull/1563" rel="noopener noreferrer"&gt;fix(kad_dht): honour protocol_prefix on the wire&lt;/a&gt; because I realized that even if you pass a custom &lt;code&gt;protocol_prefix&lt;/code&gt; to the DHT, the library was just... ignoring it. It would accept the configuration, but then it would go right ahead and speak &lt;code&gt;/ipfs/kad/1.0.0&lt;/code&gt; on the wire anyway. That’s a nightmare for anyone trying to run a private network or a specialized DHT that shouldn't be talking to the main IPFS public network. I added about 130 lines of code and ripped out 17 to ensure that the prefix actually gets propagated down to the protocol level where it belongs. It’s one of those fixes that feels small until you realize you can't build custom networks without it.&lt;/p&gt;

&lt;p&gt;The second PR, &lt;a href="https://github.com/libp2p/py-libp2p/pull/1560" rel="noopener noreferrer"&gt;fix(yamux): report a remote reset to a bounded read, not a clean EOF&lt;/a&gt;, is even more of a deep-dive into stream multiplexing. I was looking at how Yamux handles stream resets. If a remote peer sends a &lt;code&gt;RST&lt;/code&gt; (reset) frame, the local reader should know that something went wrong. However, I found that on a bounded read, the library was reporting a clean &lt;code&gt;EOF&lt;/code&gt;. That's dangerous! A clean &lt;code&gt;EOF&lt;/code&gt; tells the application "everything finished successfully," while a &lt;code&gt;RST&lt;/code&gt; should tell the application "the connection was dropped abruptly." I pushed a fix (+59/-1) to make sure we’re reporting the state correctly. It’s a subtle distinction, but it’s the difference between a reliable file transfer and a corrupted one that thinks it's finished.&lt;/p&gt;

&lt;p&gt;Both of these are currently &lt;strong&gt;OPEN&lt;/strong&gt; and waiting for the maintainers to take a look. P2P code is notoriously tricky to review, so I'm expecting some back-and-forth on the test cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues &amp;amp; Discussions
&lt;/h2&gt;

&lt;p&gt;I ended up opening four issues this week, mostly because I kept hitting walls while trying to get my environment and the library to behave.&lt;/p&gt;

&lt;p&gt;First, I had to document the DHT bug properly before fixing it. In &lt;a href="https://github.com/libp2p/py-libp2p/issues/1562" rel="noopener noreferrer"&gt;kad_dht: protocol_prefix is accepted and then ignored&lt;/a&gt;, I laid out the evidence of how the prefix was being dropped. It’s always better to have a clear issue trail before you just chuck a PR at a project.&lt;/p&gt;

&lt;p&gt;I also ran into a weird one in the &lt;code&gt;agentmesh&lt;/code&gt; repo. I was trying to run the SDK build on a machine running the Python 3.14 alpha/beta (yes, I like living on the edge), and it just exploded. I opened &lt;a href="https://github.com/google/agentmesh/issues/692" rel="noopener noreferrer"&gt;&lt;code&gt;make sdk-python&lt;/code&gt; fails on a Python 3.14 host&lt;/a&gt; because the error message was completely unhelpful—it didn't name anything relevant to the actual failure. These kinds of "future-proofing" issues are vital for keeping projects alive as the ecosystem moves forward.&lt;/p&gt;

&lt;p&gt;Back in &lt;code&gt;py-libp2p&lt;/code&gt;, I noticed that the interop CI was completely broken. It turns out the Nim echo server, which we use to test cross-language compatibility, no longer compiles. I opened &lt;a href="https://github.com/libp2p/py-libp2p/issues/1561" rel="noopener noreferrer"&gt;interop CI: the nim echo server no longer compiles, blocking every PR&lt;/a&gt; to get some eyes on it. There’s nothing more frustrating than having a perfect PR ready to go, only to have the entire CI pipeline turn red because of a dependency issue in a different language.&lt;/p&gt;

&lt;p&gt;Finally, I logged the original bug for the Yamux reset issue: &lt;a href="https://github.com/libp2p/py-libp2p/issues/1559" rel="noopener noreferrer"&gt;yamux: a bounded read on a stream the peer RESET reports a clean EOF&lt;/a&gt;. Documenting these protocol-level quirks is half the battle in open source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;This was a pure &lt;strong&gt;Python&lt;/strong&gt; and networking week. When you're working in the libp2p ecosystem, you're not just writing code; you're thinking about byte streams, frame headers, and state machines. &lt;/p&gt;

&lt;p&gt;My work was heavily focused on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend/Networking&lt;/strong&gt;: Dealing with the Kademlia DHT and Yamux multiplexer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol Design&lt;/strong&gt;: Ensuring that protocol IDs and prefixes are correctly handled on the wire.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/DX&lt;/strong&gt;: Debugging why builds fail on newer Python versions and fixing broken interop tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn't have a commit streak this week, mostly because I spent a lot of time in "research and debug" mode—reading the specs, tracing the execution flow, and writing the issues before I actually touched the code. Sometimes the best work happens when the keyboard is quiet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next week is all about the follow-through. I've got those two PRs in &lt;code&gt;py-libp2p&lt;/code&gt; that need to be shepherded through review. Protocol changes usually require a lot of justification (rightfully so!), so I'll probably be spending some time writing more exhaustive test cases to prove that my fixes don't break existing behavior.&lt;/p&gt;

&lt;p&gt;I'm also hoping to see some movement on the &lt;code&gt;agentmesh&lt;/code&gt; issue. If I can't get the SDK building on Python 3.14, I might have to go down another rabbit hole to fix the underlying build script myself. The joys of being an early adopter! &lt;/p&gt;

&lt;p&gt;Overall, I'm feeling good about the progress—cleaning up the core of a p2p library is rewarding work, even if it's invisible to most users. Onward!&lt;/p&gt;

</description>
      <category>networking</category>
      <category>python</category>
      <category>opensource</category>
      <category>ethereum</category>
    </item>
    <item>
      <title>Dev log #25 Hardening p2p networking and shipping interactive CLI features in Rust</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Tue, 20 Oct 2026 04:10:16 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-25-hardening-p2p-networking-and-shipping-interactive-cli-features-in-rust-n36</link>
      <guid>https://dev.to/yashksaini/dev-log-25-hardening-p2p-networking-and-shipping-interactive-cli-features-in-rust-n36</guid>
      <description>&lt;p&gt;Hi, glad you found your way here. I'm Yash, a developer who contributes to open source, mostly p2p networking and AI tooling. Each week I write up what I shipped, what broke, and what fixing it taught me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Sometimes the most satisfying weeks aren't about the volume of commits, but the weight of the problems you're solving. This week was a deep dive into the guts of p2p networking and CLI ergonomics, all in Rust. I managed to get 5 PRs merged across two key projects, totaling 2,203 additions. The highlight was definitely landing the new interactive mode in Trak and tightening up some potentially leaky connection logic in minip2p.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Most of my energy this week was split between two very different but equally interesting Rust projects: &lt;code&gt;minip2p&lt;/code&gt; and &lt;code&gt;Trak&lt;/code&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Hardening the p2p Layer: minip2p
&lt;/h3&gt;

&lt;p&gt;Working on &lt;code&gt;minip2p&lt;/code&gt; always feels like a battle against the inherent chaos of the internet. When you're dealing with NAT traversal and relay servers, you can't assume anything works the way it says it does. &lt;/p&gt;

&lt;p&gt;I spent a good chunk of time in the NAT and relay-server logic. One of the more critical fixes was addressing how we handle NAT renewals. I realized we were being a bit too trusting of the relay's expiration timestamps. If you trust an external server to tell you exactly when your mapping expires, you're asking for a dropped connection. I pushed a fix to schedule renewals at half the lifetime, providing a much-needed safety margin. &lt;/p&gt;

&lt;p&gt;I also dug into the &lt;code&gt;relay-server&lt;/code&gt; itself to ensure we’re properly counting finished HOP workers against the connection cap. In p2p, if you don't track your resource usage with surgical precision, you end up with "ghost" connections that eat up your capacity until the whole node crawls to a halt.&lt;/p&gt;

&lt;p&gt;On the &lt;code&gt;swarm&lt;/code&gt; side, I tackled a gnarly edge case where abandoning a peer-closed stream wasn't properly acknowledging its dropped data. It’s one of those "invisible" bugs that doesn't crash the app but leads to weird state mismatches over time. Getting that fixed (and documented) makes the whole stream lifecycle feel much more robust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Better Exploration: Trak
&lt;/h3&gt;

&lt;p&gt;On the developer tooling front, I’ve been contributing to &lt;code&gt;Trak&lt;/code&gt;. This week was all about making the tool more "explorable." &lt;/p&gt;

&lt;p&gt;The big win was landing the interactive mode. Previously, you had to know exactly what subcommand you wanted. Now, if you run &lt;code&gt;trak&lt;/code&gt; with no arguments, it drops you into an interactive session. I also mirrored the &lt;code&gt;--tree&lt;/code&gt; functionality from &lt;code&gt;callers&lt;/code&gt; over to &lt;code&gt;calls&lt;/code&gt;. When you're trying to map out a complex codebase, being able to see the call hierarchy as a tree is a game-changer. &lt;/p&gt;

&lt;p&gt;I ended up adding about 1,757 lines to Trak this week, which included a lot of work on the interactive match list ordering and driving a scripted interactive session against the binary to make sure the UI doesn't regress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests
&lt;/h2&gt;

&lt;p&gt;It was a "clean sweep" week for PRs—5 opened, 5 merged. &lt;/p&gt;

&lt;p&gt;In &lt;code&gt;minip2p&lt;/code&gt;, &lt;a href="https://github.com/deepso7/minip2p/pull/323" rel="noopener noreferrer"&gt;pull #323&lt;/a&gt; and &lt;a href="https://github.com/deepso7/minip2p/pull/322" rel="noopener noreferrer"&gt;pull #322&lt;/a&gt; were the heavy hitters for networking stability. The former dealt with that NAT renewal skepticism I mentioned, while the latter fixed the connection cap accounting in the relay server. I also landed &lt;a href="https://github.com/deepso7/minip2p/pull/321" rel="noopener noreferrer"&gt;pull #321&lt;/a&gt;, which ensures we're properly acking data when a stream is abandoned. These are the kinds of fixes that you don't "see" as a user, but you definitely feel when the network stays stable for days instead of hours.&lt;/p&gt;

&lt;p&gt;Over at &lt;code&gt;Trak&lt;/code&gt;, &lt;a href="https://github.com/subhdotsol/Trak/pull/3" rel="noopener noreferrer"&gt;pull #3&lt;/a&gt; was the massive one. It introduced the interactive mode and changed the default behavior of the CLI. It’s always a bit nerve-wracking to change the entry point of a tool, but the feedback on the whole-branch review was solid, and I think it makes the tool much more approachable for new users. I also squeezed in &lt;a href="https://github.com/subhdotsol/Trak/pull/2" rel="noopener noreferrer"&gt;pull #2&lt;/a&gt; to add the &lt;code&gt;--tree&lt;/code&gt; flag to the &lt;code&gt;calls&lt;/code&gt; command, keeping the API surface consistent across the different search modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues &amp;amp; Discussions
&lt;/h2&gt;

&lt;p&gt;While most things went smoothly, I did open one issue in &lt;code&gt;minip2p&lt;/code&gt; that's going to need some brainpower next week. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/deepso7/minip2p/issues/324" rel="noopener noreferrer"&gt;Issue #324&lt;/a&gt; is about our manual-ack backpressure tests. Right now, the tests are a bit flaky because they interpret "the link has been quiet for 500ms" as "the link has stopped." In a real-world network (or even a busy CI runner), 500ms of silence doesn't necessarily mean the backpressure is working; it might just mean the CPU was busy elsewhere. I need to find a more deterministic way to validate that the data flow has actually paused without relying on wall-clock timers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Reviews
&lt;/h2&gt;

&lt;p&gt;I also spent some time in the &lt;a href="https://github.com/NethermindEth/dotnet-libp2p" rel="noopener noreferrer"&gt;dotnet-libp2p&lt;/a&gt; repo, helping out with some code reviews. Even though my own coding this week was 100% Rust, the p2p world is small, and the concepts carry over.&lt;/p&gt;

&lt;p&gt;I approved &lt;a href="https://github.com/NethermindEth/dotnet-libp2p/pull/303" rel="noopener noreferrer"&gt;pull #303&lt;/a&gt;, which adds a fuzzing harness for core channels. Fuzzing is absolutely essential for networking code—if there's a weird state transition possible, a fuzzer will find it long before a human does. I also took a look at &lt;a href="https://github.com/NethermindEth/dotnet-libp2p/pull/299" rel="noopener noreferrer"&gt;pull #299&lt;/a&gt; regarding Yamux stream registration. Ensuring outbound frames don't get ahead of stream registration is a classic race condition in multiplexers, so it was good to see that getting tightened up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;This was a pure Rust week. 8 commits, all in Rust. &lt;/p&gt;

&lt;p&gt;The line counts tell an interesting story: +2203 / -173. Usually, I like a higher deletion ratio (nothing feels better than deleting code), but this was a heavy feature-work week for &lt;code&gt;Trak&lt;/code&gt;. Building out an interactive TUI (Text User Interface) and the associated logic naturally involves a lot of new code. &lt;/p&gt;

&lt;p&gt;I didn't hit a 7-day streak this week, but the work I did put in was high-intensity. Sometimes you need those gap days to let the p2p state machine logic settle in your head before you write the next line of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next week, I need to tackle that backpressure test flakiness in &lt;code&gt;minip2p&lt;/code&gt;. Flaky tests are the bane of my existence, and I won't feel comfortable adding more features until the CI is rock solid. I'm also planning to keep refining the interactive mode in &lt;code&gt;Trak&lt;/code&gt;—now that the foundation is there, I want to see if I can make the navigation even snappier.&lt;/p&gt;

&lt;p&gt;The p2p stack is feeling more stable, and the CLI tools are getting more powerful. It's a good place to be. See you in the next update!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>cli</category>
      <category>networking</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I Built My Friend a Mock Interviewer That Read His Rust Code</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Mon, 05 Oct 2026 13:53:30 +0000</pubDate>
      <link>https://dev.to/yashksaini/i-built-my-friend-a-mock-interviewer-that-read-his-rust-code-1gif</link>
      <guid>https://dev.to/yashksaini/i-built-my-friend-a-mock-interviewer-that-read-his-rust-code-1gif</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;grill&lt;/code&gt; is a mock technical interviewer that has read your code.&lt;/strong&gt; It runs in a terminal, on an open model, on your own laptop.&lt;/p&gt;

&lt;p&gt;I built it for my friend &lt;a href="https://github.com/AkashJana18" rel="noopener noreferrer"&gt;Akash&lt;/a&gt;. He has spent the last year writing Rust, ships TypeScript on the side, and is looking for a full-time role. The rounds he dreads are the Rust and JavaScript deep dives.&lt;/p&gt;

&lt;p&gt;Most interview prep tools ask the same textbook questions: what is a closure, explain ownership. Real senior interviews do not go that way. They go "walk me through your project", and then the interviewer points at one line you hoped nobody would notice and asks why.&lt;/p&gt;

&lt;p&gt;You cannot practise that with a question bank, because the questions depend on your code. So &lt;code&gt;grill&lt;/code&gt; does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reads his repositories&lt;/strong&gt; and finds the lines worth a question: &lt;code&gt;unsafe&lt;/code&gt; blocks, locks, lifetimes, spawned tasks, &lt;code&gt;useEffect&lt;/code&gt;, &lt;code&gt;forEach(async ...)&lt;/code&gt;, &lt;code&gt;any&lt;/code&gt;, empty &lt;code&gt;catch&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interviews him on them.&lt;/strong&gt; One question about that exact code, one follow-up that presses on the weakest part of his answer, then a grade from 0 to 4 with what he missed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remembers what he got wrong.&lt;/strong&gt; The next session draws more from his worst topics.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the scanner on four of Akash's public repositories. This is real output:&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%2Fup511l6jxyc21okamnb9.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%2Fup511l6jxyc21okamnb9.png" alt="Terminal output of grill ingest finding 116 interview-worthy spots across four repositories" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;116 spots, and the top one is in &lt;code&gt;miccli&lt;/code&gt;, his voice dictation CLI: an &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;...&amp;gt;&amp;gt;&lt;/code&gt; shared with a real-time audio callback. That is exactly the line a Rust interviewer would circle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

 

&lt;p&gt;A session looks like this. The code and location are real scanner output; paste your own question, answer and grade from a real run in place of the placeholder lines:&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="err"&gt;──&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="err"&gt;──&lt;/span&gt;
&lt;span class="n"&gt;AkashJana18&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;miccli&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="py"&gt;.rs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;62&lt;/span&gt;  &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;
&lt;span class="mi"&gt;62&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt;     &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;start_capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AudioStream&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;63&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt;         &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
   &lt;span class="err"&gt;│&lt;/span&gt;         &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="mi"&gt;72&lt;/span&gt; &lt;span class="err"&gt;│&lt;/span&gt;         &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Mutex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;PendingResampler&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="err"&gt;│&lt;/span&gt;         &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&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/yashksaini-coder" rel="noopener noreferrer"&gt;
        yashksaini-coder
      &lt;/a&gt; / &lt;a href="https://github.com/yashksaini-coder/grill" rel="noopener noreferrer"&gt;
        grill
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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/yashksaini-coder/grill/docs/cover.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%2Fyashksaini-coder%2Fgrill%2FHEAD%2Fdocs%2Fcover.png" alt="grill: a mock interviewer that has read your code" width="800"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;grill&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://github.com/sponsors/yashksaini-coder" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/905c0628741c74fd2dc51df9409ed09e4624cfed6a16787d8c2102a17f4e8506/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53706f6e736f722d2545322539442541342d6561346161613f6c6f676f3d67697468756273706f6e736f7273266c6f676f436f6c6f723d7768697465" alt="Sponsor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A mock interviewer that has read your code. It runs on your laptop, on an open model, with no network.&lt;/p&gt;

&lt;p&gt;Most interview prep asks textbook questions. Real senior interviews ask "walk me through your project", then press on the one line you hoped nobody would notice. &lt;code&gt;grill&lt;/code&gt; clones your repositories, finds those lines, and asks about them.&lt;/p&gt;

&lt;p&gt;Built for &lt;a href="https://github.com/AkashJana18" rel="noopener noreferrer"&gt;Akash&lt;/a&gt;, who writes Rust and TypeScript and is interviewing for full-time roles.&lt;/p&gt;

&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/yashksaini-coder/grill/docs/desk.jpg"&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%2Fyashksaini-coder%2Fgrill%2FHEAD%2Fdocs%2Fdesk.jpg" alt="I built my friend an interviewer that read his code" width="800"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;── 1 of 5 ──
AkashJana18/miccli src/audio.rs:62  shared-state
62 │     pub fn start_capture(&amp;amp;self) -&amp;gt; Result&amp;lt;AudioStream&amp;gt; {
63 │         let (tx, rx) = mpsc::channel();
   │         ...
72 │         let sink = Arc::new(Mutex::new(PendingResampler::new(
   │         ...

Why is `sink` behind a Mutex, and what does the audio callback do if that lock is contended?
  &amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The code and location above are real scanner output. The question is an example of the kind a model asks; yours will differ.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Demo&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/yashksaini-coder/grill/docs/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fyashksaini-coder%2Fgrill%2FHEAD%2Fdocs%2Fdemo.gif" alt="grill interviewing a real repository: ingest, spots, a two-round interview, the weak-spot map"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recorded…&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/yashksaini-coder/grill" 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;About 1,300 lines of TypeScript including tests, three runtime dependencies, MIT licensed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull gemma3:4b

git clone https://github.com/yashksaini-coder/grill &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;grill
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;link

&lt;/span&gt;grill ingest AkashJana18/miccli AkashJana18/solana-consensus-lab
grill start                    &lt;span class="c"&gt;# 5 questions&lt;/span&gt;
grill start &lt;span class="nt"&gt;-n&lt;/span&gt; 3 &lt;span class="nt"&gt;--lang&lt;/span&gt; rust   &lt;span class="c"&gt;# Rust only&lt;/span&gt;
grill report                   &lt;span class="c"&gt;# the weak-spot map&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&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%2Fo7mmx0ocyxryp72z98la.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%2Fo7mmx0ocyxryp72z98la.png" alt="Architecture of grill: ingest, interview and remember stages, all running locally on Ollama, Gemma and Mastra" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The open pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://ai.google.dev/gemma" rel="noopener noreferrer"&gt;Gemma&lt;/a&gt;&lt;/strong&gt;, Google's open-weight model, served by &lt;a href="https://ollama.com" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt; on &lt;code&gt;localhost&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://mastra.ai/docs" rel="noopener noreferrer"&gt;Mastra&lt;/a&gt;&lt;/strong&gt;, the open-source TypeScript agent framework, for the two agents and the tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Design rule: the model gets one small job at a time
&lt;/h3&gt;

&lt;p&gt;A 4B model running on a laptop is not going to plan an interview, hold a rubric in its head, and remember what it asked five turns ago. So I did not ask it to. The order of steps lives in ordinary TypeScript. The model is only ever asked to do one of three things: write a question, write a follow-up, or grade an exchange.&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%2Fobzwfj1p08ywsxmkl296.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%2Fobzwfj1p08ywsxmkl296.png" alt="One interview round: scanner shows a snippet, interviewer asks, candidate answers, interviewer follows up, grader scores, store saves" width="799" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every one of those calls is handed the real snippet again. The model never has to recall code, so it has less room to invent it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: finding the lines worth asking about
&lt;/h3&gt;

&lt;p&gt;The scanner is a table of 38 patterns, 17 for Rust and 21 for JavaScript and TypeScript. Each one carries a topic, a weight, and the angle a good interviewer would take:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;rust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lock&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="s2"&gt;concurrency&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.(&lt;/span&gt;&lt;span class="sr"&gt;lock|read|write&lt;/span&gt;&lt;span class="se"&gt;)\(\)\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(\.&lt;/span&gt;&lt;span class="sr"&gt;await|&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;unwrap&lt;/span&gt;&lt;span class="se"&gt;\(\)&lt;/span&gt;&lt;span class="sr"&gt;|&lt;/span&gt;&lt;span class="se"&gt;\?)&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;how long the guard lives, whether it is held across an await, and what a poisoned or contended lock does&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="nf"&gt;js&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async-in-loop&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="s2"&gt;async&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.(&lt;/span&gt;&lt;span class="sr"&gt;forEach|map|filter|reduce&lt;/span&gt;&lt;span class="se"&gt;)\(\s&lt;/span&gt;&lt;span class="sr"&gt;*async&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;what actually awaits here, the order of completion, and how errors surface&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;A regex hit on one line is not a question. So for every hit, the scanner walks up to the enclosing function, matches braces to find its end, and frames the whole thing. Weights are summed per function, overlapping frames are dropped in favour of the strongest, and each repository contributes its best 40 spots, round-robin across topics so a codebase full of &lt;code&gt;.unwrap()&lt;/code&gt; does not turn the whole interview into error handling.&lt;/p&gt;

&lt;p&gt;This is regex and brace counting, not a parser. It finds interesting code. It does not understand it. Understanding is the model's job.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2: two Mastra agents on Gemma
&lt;/h3&gt;

&lt;p&gt;Mastra talks to Ollama through its OpenAI-compatible endpoint, so there is no provider SDK and no API key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;providerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;local&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;local&lt;/span&gt;&lt;span class="dl"&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;interviewer&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;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;interviewer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;interviewer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;INTERVIEWER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&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;grader&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;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GRADER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;interviewer&lt;/strong&gt; is told to ask exactly one question, to name an identifier from the snippet so the candidate knows it read the code, and never to answer or hint.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;grader&lt;/strong&gt; is a separate agent with a separate prompt. It did not write the question, so it has no stake in the answer being good. It returns JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"one sentence"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"missed"&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;"specific point"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"better"&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 answer you wanted"&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;Small models wrap JSON in prose and code fences. Instead of trusting the reply, &lt;code&gt;grill&lt;/code&gt; pulls out the first balanced &lt;code&gt;{...}&lt;/code&gt;, validates it with zod, and if that fails, shows the model its own bad reply and asks once more.&lt;/p&gt;

&lt;p&gt;There is also an opt-in &lt;code&gt;read_source&lt;/code&gt; tool (&lt;code&gt;grill start --tools&lt;/code&gt;). It lets the interviewer read beyond the snippet, for example to see a caller or a type definition. It is confined to the repository: a path that resolves outside the checkout is refused. It needs a model with tool calling, so it is off by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 3: the weak-spot map
&lt;/h3&gt;

&lt;p&gt;Every round is saved as JSON under &lt;code&gt;~/.grill/sessions&lt;/code&gt;. The next session picks spots with a weighted draw:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Hotspot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&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;stats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TopicStat&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="kr"&gt;number&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;stat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;spot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topic&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;weakness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stat&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&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;repeat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spot&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="p"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Squared, so the first session opens on the richest code, not on filler.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;spot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;weakness&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;repeat&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;Score a 1 on lifetimes and lifetimes come back. Score a 4 on &lt;code&gt;unsafe&lt;/code&gt; and it fades. Spots already asked are rare but not banned, because being asked the same thing twice is how you find out whether you learned it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing an LLM app without an LLM
&lt;/h3&gt;

&lt;p&gt;I did not want the test suite to need a GPU. The tests start a fake OpenAI-compatible server on a random local port and point the real Mastra agents at it. That covers a full round, the JSON retry, &lt;code&gt;:skip&lt;/code&gt; and &lt;code&gt;:quit&lt;/code&gt;, and the tool call including the path-escape refusal. 18 tests, a few seconds, no model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;For this project it is not a preference. A closed API would have made it a worse tool for Akash in four concrete ways.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;His fumbled answers stay his.&lt;/strong&gt; Interview practice only works if you are willing to be wrong. &lt;code&gt;grill&lt;/code&gt; stores every weak answer he gives, by design. That file sits in &lt;code&gt;~/.grill&lt;/code&gt; on his disk. With a hosted API, the same record of everything he does not know would be on someone else's server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Private code works.&lt;/strong&gt; &lt;code&gt;grill ingest&lt;/code&gt; takes a local path. Work code under NDA, the repository he cannot push anywhere, the side project he is not ready to show: all of it can be interviewed on without leaving the machine. Once the model is pulled, a local-path session needs no network at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice is free, so he can do a lot of it.&lt;/strong&gt; Three model calls per question, five questions per session, as many sessions as it takes. On a metered API that is a bill that grows with exactly the behaviour I want to encourage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;He can change the interviewer.&lt;/strong&gt; The rubric is a string in &lt;code&gt;src/interview/mastra-brain.ts&lt;/code&gt;. If he is preparing for a company that cares about system design more than language trivia, he edits the prompt. If he gets a better laptop, &lt;code&gt;GRILL_MODEL&lt;/code&gt; swaps in a bigger model in one environment variable. If Ollama is not his thing, &lt;code&gt;GRILL_URL&lt;/code&gt; points at llama.cpp or LM Studio. Nothing in the tool is tied to one vendor.&lt;/p&gt;

&lt;p&gt;Open weights made it private and free. An open framework made it changeable. He needs all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits, honestly
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The scanner cannot tell a deliberate &lt;code&gt;unwrap()&lt;/code&gt; from a careless one. It surfaces candidates; some are dull.&lt;/li&gt;
&lt;li&gt;Question quality is the model's. A larger local model asks sharper questions.&lt;/li&gt;
&lt;li&gt;The grade is a study aid, not a verdict. Read the "missed" list, not the number.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma.&lt;/strong&gt; Gemma runs locally through Ollama and does all three jobs: asking, pressing, and grading. The whole design, with fixed orchestration and one small task per call, exists to make a small open-weight model enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Mastra.&lt;/strong&gt; Two Mastra agents over an open model, plus a sandboxed &lt;code&gt;read_source&lt;/code&gt; tool, wired to a local OpenAI-compatible endpoint with no provider SDK.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Dev log #24 Hardening the p2p stack: From flaky tests to WebSocket fixes</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Mon, 05 Oct 2026 09:12:47 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-24-hardening-the-p2p-stack-from-flaky-tests-to-websocket-fixes-55f</link>
      <guid>https://dev.to/yashksaini/dev-log-24-hardening-the-p2p-stack-from-flaky-tests-to-websocket-fixes-55f</guid>
      <description>&lt;p&gt;Hi, glad you found your way here. I'm Yash, a developer who contributes to open source, mostly p2p networking and AI tooling. Each week I write up what I shipped, what broke, and what fixing it taught me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;If you've ever had a CI run fail because a test expected a packet to arrive in exactly 200ms, you know my pain this week. I spent most of my time moving away from clock-based testing and toward progress-driven logic, alongside some critical bug fixes in the Python libp2p stack. Across 22 commits and 15 code reviews, it was a heavy week for p2p infrastructure and making sure our networking primitives actually behave when the network gets noisy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Most of my energy went into &lt;strong&gt;minip2p&lt;/strong&gt;, where I’m deep in the weeds of making the Rust implementation more resilient. I pushed 14 commits there, but the real story is in the refactoring.&lt;/p&gt;

&lt;h3&gt;
  
  
  minip2p: Killing the Flaky Test
&lt;/h3&gt;

&lt;p&gt;There’s nothing that kills developer velocity quite like a flaky test suite. I opened and closed Issue #261 this week because our timing-budgeted tests were falling over whenever the CI runner got too busy. We’ve all been there: you put a &lt;code&gt;sleep&lt;/code&gt; in a test, it works on your machine, and then it fails in GitHub Actions because the runner is under-provisioned.&lt;/p&gt;

&lt;p&gt;I landed PR #284 to address this. Instead of waiting for the clock, I refactored the tests to be driven by progress. For the TCP transport tests, I stopped waiting for an arbitrary 200ms and started waiting for the socket to actually fill and drain. I also had to isolate the relay reservation and timer-bound tests in &lt;code&gt;nextest&lt;/code&gt; to ensure they aren't fighting for resources. It’s the kind of unglamorous work that makes the whole project feel more professional.&lt;/p&gt;

&lt;p&gt;Beyond the tests, I landed a pretty significant refactor in PR #260. I wanted to consolidate how we handle framed exchanges. Now, AutoNAT and Identify (two core p2p protocols) share a framed exchange and are properly bounded by their declared lengths. It’s a bit of "defensive networking"—ensuring a peer can't just stream infinite data at us during a handshake.&lt;/p&gt;

&lt;h3&gt;
  
  
  py-libp2p: Fixing the Leaks
&lt;/h3&gt;

&lt;p&gt;On the Python side of things, I spent some time in &lt;strong&gt;py-libp2p&lt;/strong&gt;. I noticed a gnarly issue where the WebSocket transport was being a bit too generic with its error reporting. Basically, every failure after the initial connection was being swallowed and reported as a "handshake timeout," which is incredibly frustrating when you're trying to debug a real network issue. Even worse, it was leaking the TCP socket in the process.&lt;/p&gt;

&lt;p&gt;I closed Issue #1554 by landing PR #1556, which ensures we report the &lt;em&gt;real&lt;/em&gt; dial failure and, crucially, close the socket so we don't bleed file descriptors. I also followed up with PR #1555 to make sure we’re actually verifying server certificates on &lt;code&gt;wss://&lt;/code&gt; (secure WebSocket) dials. Security isn't optional, even in p2p.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trak: Cleaning up the Indexer
&lt;/h3&gt;

&lt;p&gt;I also made a quick pit stop at &lt;strong&gt;Trak&lt;/strong&gt;, a Rust project I've been helping out with. I noticed the indexer was getting distracted by test fixtures. If you have a directory full of mock code for tests, you don't necessarily want your primary code indexer treating those as real declarations. I landed PR #1 by updating the logic to read declarations from source code only and explicitly exclude test fixture trees. It’s a small change (+532/-12), but it makes the tool much more accurate for its users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests
&lt;/h2&gt;

&lt;p&gt;It was a "clean sweep" week for PRs—5 opened, 5 merged.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;[minip2p #284]&lt;/strong&gt;: The big test overhaul. Moving from &lt;code&gt;sleep()&lt;/code&gt; to actual state-checking.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;[minip2p #260]&lt;/strong&gt;: The core refactor. Sharing framed exchanges between protocols.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;[py-libp2p #1556]&lt;/strong&gt;: Fixing the WebSocket socket leak and improving error transparency.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;[py-libp2p #1555]&lt;/strong&gt;: Enforcing WSS certificate verification.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;[Trak #1]&lt;/strong&gt;: Narrowing the indexer's scope to exclude noise.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code Reviews
&lt;/h2&gt;

&lt;p&gt;While I only had 22 commits of my own, I spent a massive amount of time in the &lt;strong&gt;dotnet-libp2p&lt;/strong&gt; repository. I provided 15 reviews this week, and honestly, this is where a lot of the "architectural" thinking happens. Helping the C# implementation stay in sync with the wider libp2p spec is a challenge, but the team there is shipping some great stuff.&lt;/p&gt;

&lt;p&gt;Some of the highlights from the review pile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;QUIC on Windows&lt;/strong&gt;: Reviewed a PR to enable the QUIC transport for Windows users.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Gossipsub Hardening&lt;/strong&gt;: Looked at changes for handling partial messages and using &lt;code&gt;IDONTWANT&lt;/code&gt; for large gossip messages to save bandwidth.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Security&lt;/strong&gt;: Approved a move to P-256 ephemeral keys for TLS and better validation of gossip signatures before they hit application hooks.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Reliability&lt;/strong&gt;: Gave the green light on PRs for preserving Yamux replies and handling segmented writes more gracefully.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reviewing 15 PRs while only opening 5 of my own definitely made this a "mentorship and alignment" week. It's important to keep the ecosystem moving together, especially when you're working across three different languages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;This week was a polyglot special:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Rust&lt;/strong&gt;: My main focus for the heavy lifting in &lt;code&gt;minip2p&lt;/code&gt; and &lt;code&gt;Trak&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Python&lt;/strong&gt;: Used for the &lt;code&gt;py-libp2p&lt;/code&gt; maintenance and bug fixing.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;C#&lt;/strong&gt;: Purely on the review side, ensuring the .NET implementation is solid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The numbers tell a story of growth: &lt;strong&gt;+2,503 / -357&lt;/strong&gt;. That’s a lot of new code, but a good chunk of that was the test infrastructure and the refactored protocol handlers. I'm okay with a net positive line count when it means the tests are finally stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next week, I want to double down on the &lt;code&gt;minip2p&lt;/code&gt; transport layer. Now that the tests are stable, I can actually start benchmarking performance without worrying about the CI runner's mood. I'm also keeping an eye on the open PRs in the Python and .NET stacks—there's a lot of momentum right now in the p2p space, and I want to make sure we keep that energy going.&lt;/p&gt;

&lt;p&gt;If you're building p2p tools, stop using &lt;code&gt;sleep()&lt;/code&gt; in your tests. Your future self (and your CI) will thank you. See you next week!&lt;/p&gt;




&lt;p&gt;&lt;b&gt;Generated by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;

</description>
      <category>networking</category>
      <category>development</category>
      <category>programming</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Dev log #23 Polishing the stack: Diagrams, async shutdowns, and a whole lot of code reviews</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sun, 27 Sep 2026 14:02:10 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-polishing-the-stack-diagrams-async-shutdowns-and-a-whole-lot-of-code-reviews-162b</link>
      <guid>https://dev.to/yashksaini/dev-log-polishing-the-stack-diagrams-async-shutdowns-and-a-whole-lot-of-code-reviews-162b</guid>
      <description>&lt;p&gt;Hi, glad you found your way here. I'm Yash, a developer who contributes to open source, mostly p2p networking and AI tooling. Each week I write up what I shipped, what broke, and what fixing it taught me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;This week was all about clarity—both in the code and how we explain it. I spent a significant chunk of time in the docs and refactoring event flows, while also diving deep into code reviews for the C# libp2p ecosystem. 15 commits, 5 PRs, and 12 code reviews later, the p2p stack feels a lot more solid.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Most of my energy this week went into &lt;strong&gt;minip2p&lt;/strong&gt;. It’s one thing to build a p2p library in Rust; it’s another thing entirely to make sure people actually understand how to use it. I spent a lot of time in &lt;code&gt;docs/pages&lt;/code&gt; and &lt;code&gt;docs/components&lt;/code&gt;, trying to visualize the invisible.&lt;/p&gt;

&lt;p&gt;I tackled a major refactor in the core crate, specifically routing all capabilities through Endpoint events. This was a hefty PR—1,365 additions and 939 deletions—aimed at making the internal event system more predictable. When you're dealing with p2p networking, state management is everything. Moving toward a more event-driven architecture for capabilities simplifies the mental model significantly.&lt;/p&gt;

&lt;p&gt;On the documentation front, I realized the landing page needed more "meat." I built out diagrams to explain the connection attempt flow and the event loop. I used Tailwind to style these diagrams directly, ensuring they look great and stay responsive. I also spent time correcting some claims about the event loop and &lt;code&gt;unsafe&lt;/code&gt; usage—it's vital to be precise when you're talking about Rust's safety guarantees. I even made sure to neutralize animation delays for users with reduced motion settings because accessibility shouldn't be an afterthought in dev tools.&lt;/p&gt;

&lt;p&gt;I also jumped over to &lt;strong&gt;dotnet-libp2p&lt;/strong&gt; to handle some protocol-level stability. I implemented async shutdown for the pubsub router and discovery protocols. In a distributed system, how you leave the network is just as important as how you join it. Without proper async teardown, you end up with hanging resources or weird state issues on the peer side. While I was there, I bumped &lt;code&gt;Microsoft.SourceLink.GitHub&lt;/code&gt; to clear up a pesky NU1902 restore failure that was gunking up the CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests
&lt;/h2&gt;

&lt;p&gt;I had five PRs on the move this week, and I'm happy to say four of them are already merged.&lt;/p&gt;

&lt;p&gt;The big one was &lt;a href="https://github.com/deepso7/minip2p/pull/203" rel="noopener noreferrer"&gt;Route all capabilities through Endpoint events (#177)&lt;/a&gt;. It was a deep-tissue refactor of the &lt;code&gt;minip2p&lt;/code&gt; core. It’s always a bit nerve-wracking to move over 2,000 lines of code in a networking stack, but the result is a much cleaner interface for how we handle peer capabilities.&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%2Fy2twpbgpztq8oe8s2hew.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%2Fy2twpbgpztq8oe8s2hew.png" alt="Minip2p Landing page" width="799" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the Python world, I opened an enhancement for &lt;strong&gt;py-libp2p&lt;/strong&gt; to &lt;a href="https://github.com/libp2p/py-libp2p/pull/1544" rel="noopener noreferrer"&gt;randomise per-round lookup candidate selection&lt;/a&gt; in the Kademlia DHT. Randomization is a small change—only about 150 lines—but it’s a huge win for network resilience. It prevents the lookup process from getting stuck in a loop or being biased toward the same set of peers every time. That one is still open and undergoing review.&lt;/p&gt;

&lt;p&gt;The rest were mostly focused on the C# side, getting those async shutdowns merged and fixing the build pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues &amp;amp; Discussions
&lt;/h2&gt;

&lt;p&gt;I spent some time clearing the deck on the issue tracker for &lt;code&gt;minip2p&lt;/code&gt;. I opened and subsequently closed &lt;a href="https://github.com/deepso7/minip2p/issues/209" rel="noopener noreferrer"&gt;Add UI sections to the landing page&lt;/a&gt;. This was the driver for all that Tailwind and diagram work I mentioned earlier. It’s satisfying to see a "docs" issue result in tangible, visual improvements to a project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Reviews
&lt;/h2&gt;

&lt;p&gt;If I wasn't writing code this week, I was reading it. I gave 12 code reviews, almost exclusively in the &lt;strong&gt;dotnet-libp2p&lt;/strong&gt; repo. &lt;/p&gt;

&lt;p&gt;The Nethermind team has been crushing it, and I spent a lot of time reviewing their Gossipsub implementation. We looked at everything from &lt;a href="https://github.com/NethermindEth/dotnet-libp2p/pull/213" rel="noopener noreferrer"&gt;bounding Gossipsub control-plane work&lt;/a&gt; to &lt;a href="https://github.com/NethermindEth/dotnet-libp2p/pull/210" rel="noopener noreferrer"&gt;supporting partial messages&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Reviewing 12 PRs while only opening 5 of my own definitely made this a "mentorship and maintenance" week. I approved work on TLS remote public key exposure, pubsub TTL cache eviction fixes, and hardening strict pubsub no-sign validation. It’s a lot of specialized p2p logic, but keeping the C# implementation at parity with the Go and Rust versions is a massive goal for the ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;It was a polyglot week, leaning heavily into the typed world.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Rust&lt;/strong&gt;: My primary focus for &lt;code&gt;minip2p&lt;/code&gt;. I spent a lot of time balancing core logic with documentation.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;C#&lt;/strong&gt;: Used for the protocol-level work in &lt;code&gt;dotnet-libp2p&lt;/code&gt;. The async/await pattern in C# is a different beast compared to Rust's futures, but it's equally powerful for networking.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Python&lt;/strong&gt;: Just a touch of it for the Kademlia DHT work in &lt;code&gt;py-libp2p&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With 2,661 additions and 1,439 deletions, it was a net-positive week in terms of LOC, but a lot of that "weight" is in the new documentation and diagrams. The refactors actually helped trim some of the fat in the core logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next week, I want to see that Kademlia randomization PR in &lt;code&gt;py-libp2p&lt;/code&gt; get across the finish line. I’m also planning to dive back into the &lt;code&gt;minip2p&lt;/code&gt; Rust crates to see if there are more areas where I can simplify the event logic now that the capability routing is merged. &lt;/p&gt;

&lt;p&gt;There's always more to document, but for now, I'm just happy the landing page finally explains the event loop without me having to jump into a Discord call to draw it on a virtual whiteboard!&lt;/p&gt;




</description>
      <category>networking</category>
      <category>opensource</category>
      <category>programming</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Dev log #22 Tearing out the old: Deleting 3,800 lines of legacy p2p code</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Mon, 21 Sep 2026 04:00:59 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-22-tearing-out-the-old-deleting-3800-lines-of-legacy-p2p-code-3n0i</link>
      <guid>https://dev.to/yashksaini/dev-log-22-tearing-out-the-old-deleting-3800-lines-of-legacy-p2p-code-3n0i</guid>
      <description>&lt;p&gt;Hi, glad you found your way here. I'm Yash, a developer who contributes to open source, mostly p2p networking and AI tooling. Each week I write up what I shipped, what broke, and what fixing it taught me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;There is no feeling quite like watching a PR diff show thousands of red lines and only a handful of green ones. This week was all about that catharsis. I spent my time simplifying the p2p stack, specifically by ripping out legacy protocols in Rust and hardening storage layers in Python. Across 5 commits and 3 PRs, I managed to delete nearly 4,000 lines of code while only adding about 160. It was a week for codebase health and architectural clarity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Most of my energy this week went into &lt;strong&gt;minip2p&lt;/strong&gt;, a Rust-based peer-to-peer networking stack I've been contributing to. If you’ve spent any time in the libp2p ecosystem, you know that Floodsub is the "old way" of doing pubsub—it’s simple, but it’s incredibly noisy and inefficient because it just broadcasts everything to everyone. Gossipsub has been the standard for a long time now, and maintaining both in a "mini" implementation started to feel like unnecessary baggage.&lt;/p&gt;

&lt;p&gt;I pushed 3 commits to &lt;code&gt;minip2p&lt;/code&gt; that effectively scrubbed Floodsub from the entire project. This wasn't just a "delete the file" kind of task; it was a breaking change that rippled through the public API and the documentation. I had to touch 53 files to make it happen. The commit messages tell the story: I started by dropping it from the README and the docs site, then moved into the core pubsub logic to make Gossipsub the only available router, and finally cleaned up the bindings so users don't even have to (or get to) select a router anymore. It’s a breaking change—marked with the &lt;code&gt;!&lt;/code&gt; in my commit messages—but the codebase is so much leaner for it. We're talking 141 additions against 3,876 deletions. That is a lot of technical debt gone in one go.&lt;/p&gt;

&lt;p&gt;On the Python side, I spent some time with &lt;strong&gt;py-libp2p&lt;/strong&gt;. This was less about architectural shifts and more about the "un-glamorous" work that keeps a project viable: fixing flaky tests. I’ve been annoyed by &lt;code&gt;test_echo_thin_waist&lt;/code&gt; for a bit because of how it handled subprocesses. If a test doesn't reap its children properly, you end up with zombie processes and weird CI failures that have nothing to do with your actual code. I pushed 2 commits to ensure we're reaping those echo subprocesses cleanly. It’s a small change—18 lines added—but it makes the local dev experience and the CI pipeline significantly more stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests
&lt;/h2&gt;

&lt;p&gt;I had three major PRs in flight this week, two of which are already merged.&lt;/p&gt;

&lt;p&gt;The big one was &lt;a href="https://github.com/deepso7/minip2p/pull/199" rel="noopener noreferrer"&gt;Remove Floodsub from every public layer (#199)&lt;/a&gt; in &lt;code&gt;minip2p&lt;/code&gt;. This was the culmination of the work I mentioned above. Landing a PR that deletes 4,258 lines (including some generated code and docs) feels like a massive win. It simplifies the mental model for anyone new coming into the repo. You don't have to ask "which pubsub should I use?" anymore. There's only one, and it's the one that works at scale.&lt;/p&gt;

&lt;p&gt;I also landed a fix in &lt;code&gt;py-libp2p&lt;/code&gt;: &lt;a href="https://github.com/libp2p/py-libp2p/pull/1542" rel="noopener noreferrer"&gt;test(examples): reap echo subprocess cleanly in test_echo_thin_waist (fixes #1541)&lt;/a&gt;. This addressed a specific issue where the echo example tests were leaving processes hanging. It’s a small fix but a necessary one for keeping the "thin waist" of the protocol stack actually thin and manageable.&lt;/p&gt;

&lt;p&gt;Finally, I have an open PR over at &lt;strong&gt;sixb&lt;/strong&gt; titled &lt;a href="https://github.com/sixb-ai/sixb/pull/612" rel="noopener noreferrer"&gt;storage: durable, swept file upload sessions for pg and sqlite&lt;/a&gt;. This is a much heavier lift on the "new feature" side of things. I’m building out a more resilient way to handle file uploads, ensuring that sessions are durable across restarts and properly "swept" (cleaned up) when they expire. It’s currently at 1,493 additions, which is where most of my "new" code lived this week. I'm supporting both PostgreSQL and SQLite, which always adds a layer of complexity when you're trying to keep the abstraction clean. It’s still in review, but I’m looking forward to getting this landed to make the storage layer more robust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Reviews
&lt;/h2&gt;

&lt;p&gt;I managed to get into the reviewer's seat twice this week, which I always try to do to stay connected with what else is happening in the ecosystem.&lt;/p&gt;

&lt;p&gt;First, I looked at a PR in &lt;strong&gt;agent-orchestrator&lt;/strong&gt;: &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/5137" rel="noopener noreferrer"&gt;feat(packaging): support Arch Linux and stop self-updates on system installs&lt;/a&gt;. Packaging is one of those things that is easy to get wrong, especially when it comes to "self-updating" logic. You really don't want a binary trying to update itself if it was installed via &lt;code&gt;pacman&lt;/code&gt;. I approved this one as it makes the tool a much better citizen on Linux systems.&lt;/p&gt;

&lt;p&gt;I also reviewed a PR in &lt;strong&gt;py-libp2p&lt;/strong&gt;: &lt;a href="https://github.com/libp2p/py-libp2p/pull/1139" rel="noopener noreferrer"&gt;eg/1130-add-gossipsub-comparison-and-standalone-examples&lt;/a&gt;. This was a nice coincidence given my work on &lt;code&gt;minip2p&lt;/code&gt;. It adds better examples and comparisons for Gossipsub, which is exactly what the project needs as we steer people away from older protocols. Good docs and examples are the best way to prevent people from reaching for the wrong tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;This was a classic polyglot week for me, split between &lt;strong&gt;Rust&lt;/strong&gt; and &lt;strong&gt;Python&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Rust work was deep and structural. When you're working with p2p protocols, Rust’s type system is a godsend for making sure you don't leave any edge cases unhandled when ripping out a whole protocol layer. The net-negative line count this week is a badge of honor. Refactoring isn't always glamorous, but it’s how you keep a project from collapsing under its own weight.&lt;/p&gt;

&lt;p&gt;The Python work was more about the glue and the storage logic. Python is great for iterating on things like storage sessions and test harnesses where you want to move fast. Even though I was adding code in my &lt;code&gt;sixb&lt;/code&gt; PR, the overall week was still dominated by the massive deletions in &lt;code&gt;minip2p&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next week, I’m hoping to get that &lt;code&gt;sixb&lt;/code&gt; storage PR merged. It’s a big chunk of code, and I want to make sure the "sweeping" logic for old sessions is rock solid before it hits production. I'll also be keeping an eye on &lt;code&gt;minip2p&lt;/code&gt; to see if my Floodsub removal caused any unexpected friction for downstream users—though, honestly, I think everyone will be happy to see it gone.&lt;/p&gt;

&lt;p&gt;There's also some more work to do on the &lt;code&gt;py-libp2p&lt;/code&gt; test suite. Now that the echo tests are stable, I want to look at some of the other long-running examples that occasionally time out in CI. It’s a never-ending battle, but someone’s gotta do it!&lt;/p&gt;




</description>
      <category>productivity</category>
      <category>discuss</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Dev log #21 Hardening WebRTC-Direct and solving SCM leaks</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sat, 19 Sep 2026 05:49:17 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-hardening-webrtc-direct-and-solving-scm-leaks-i5k</link>
      <guid>https://dev.to/yashksaini/dev-log-hardening-webrtc-direct-and-solving-scm-leaks-i5k</guid>
      <description>&lt;p&gt;Hi, glad you found your way here. I'm Yash, a developer who contributes to open source, mostly p2p networking and AI tooling. Each week I write up what I shipped, what broke, and what fixing it taught me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;This week was all about the "glue" that holds distributed systems together. I spent my time jumping between the Python implementation of libp2p and a Go-based agent orchestrator. It was a week of deep-dives into networking internals—specifically getting WebRTC-Direct to behave across different language implementations—and fixing some frustrating SCM polling issues. Across 8 commits and 11 PRs, I focused on making sure connections are actually closed when they're supposed to be and that our agents aren't hallucinating CI failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Most of my energy went into &lt;a href="https://github.com/libp2p/py-libp2p" rel="noopener noreferrer"&gt;py-libp2p&lt;/a&gt;, where I’m currently neck-deep in WebRTC-Direct work. Networking is one of those things where it works 99% of the time, and then that last 1% takes up 90% of your brainpower.&lt;/p&gt;

&lt;h3&gt;
  
  
  Networking Internals (py-libp2p)
&lt;/h3&gt;

&lt;p&gt;I pushed 6 commits to &lt;code&gt;py-libp2p&lt;/code&gt; this week, mostly centered on stabilizing the transport layer. One of the big wins was fixing a race condition in the WebRTC dialer where ICE credentials weren't being set correctly before the first-contact replay. This was specifically blocking interoperability between Go and Python nodes—one of those gnarly "it works in isolation but fails in the wild" bugs. I also spent some time on the housekeeping side, fixing socket leaks in both &lt;code&gt;mplex&lt;/code&gt; and the &lt;code&gt;swarm&lt;/code&gt;. If you've ever wondered why your long-running nodes eventually run out of file descriptors, it’s usually because of unclosed secured connections. I made sure that when we shut down, we actually release the underlying sockets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent Orchestration (agent-orchestrator)
&lt;/h3&gt;

&lt;p&gt;Over in &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator" rel="noopener noreferrer"&gt;agent-orchestrator&lt;/a&gt;, the work was more about the developer experience and SCM integration. I tackled a weird edge case where workspace files with backslashes in their names were resolving incorrectly on Linux and macOS. It’s a classic cross-platform path handling headache, but getting it right means one less "it works on my machine" conversation. I also looked into how the orchestrator handles GitHub polling—specifically an issue where it was re-listing every open PR because the &lt;code&gt;since&lt;/code&gt; parameter wasn't being respected by the endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests
&lt;/h2&gt;

&lt;p&gt;It was a heavy PR week—11 in total. I had 4 merged, 3 still open, and 4 closed as I refined my approach.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;py-libp2p&lt;/code&gt; world, &lt;a href="https://github.com/libp2p/py-libp2p/pull/1513" rel="noopener noreferrer"&gt;PR #1513&lt;/a&gt; was a big documentation push. I documented the STUN listener versus the SDP harness and provided guidance on v1 vs v2 dialing. Clear docs are just as important as clean code when you’re building p2p primitives. I also have &lt;a href="https://github.com/libp2p/py-libp2p/pull/1532" rel="noopener noreferrer"&gt;PR #1532&lt;/a&gt; open, which introduces a true ICE-Lite listener for WebRTC-Direct. This is a significant addition (+166 lines) that will make the listener much more robust.&lt;/p&gt;

&lt;p&gt;On the bug-squashing front, &lt;a href="https://github.com/libp2p/py-libp2p/pull/1497" rel="noopener noreferrer"&gt;PR #1497&lt;/a&gt; and &lt;a href="https://github.com/libp2p/py-libp2p/pull/1495" rel="noopener noreferrer"&gt;PR #1495&lt;/a&gt; landed to fix the connection shutdown and WebRTC dial issues I mentioned earlier. &lt;/p&gt;

&lt;p&gt;For &lt;code&gt;agent-orchestrator&lt;/code&gt;, I had a few PRs that I ended up closing (&lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/5089" rel="noopener noreferrer"&gt;#5089&lt;/a&gt;, &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/5088" rel="noopener noreferrer"&gt;#5088&lt;/a&gt;, &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/5057" rel="noopener noreferrer"&gt;#5057&lt;/a&gt;, and &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/5056" rel="noopener noreferrer"&gt;#5056&lt;/a&gt;). Sometimes you start a fix and realize there's a better architectural way to handle it, or you're iterating fast and a better solution emerges. I’d rather close a PR and open a better one than merge something that isn't quite right. However, &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/5055" rel="noopener noreferrer"&gt;PR #5055&lt;/a&gt; did land, which fixed that backslash filename bug on non-Windows systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues &amp;amp; Discussions
&lt;/h2&gt;

&lt;p&gt;I opened and closed 6 issues this week, which really tells the story of the week's detective work. &lt;/p&gt;

&lt;p&gt;The most interesting one is &lt;a href="https://github.com/libp2p/py-libp2p/issues/1498" rel="noopener noreferrer"&gt;Issue #1498&lt;/a&gt; regarding residual unclosed-socket leaks. This is currently gating our &lt;code&gt;filterwarnings&lt;/code&gt; guard in the test suite. Until we can guarantee the tests aren't leaking resources, we can't turn on the stricter CI checks. It's a bit of a slog, but it's the kind of maintenance that prevents technical debt from spiraling.&lt;/p&gt;

&lt;p&gt;I also opened &lt;a href="https://github.com/libp2p/py-libp2p/issues/1512" rel="noopener noreferrer"&gt;Issue #1512&lt;/a&gt; for the ICE-Lite listener work. It's one of those features that started as a "nice to have" and quickly became a "need to have" for proper WebRTC-Direct support.&lt;/p&gt;

&lt;p&gt;On the &lt;code&gt;agent-orchestrator&lt;/code&gt; side, &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/issues/5046" rel="noopener noreferrer"&gt;Issue #5046&lt;/a&gt; was a fun find: the Copilot adapter was adding a permanent line to &lt;code&gt;.git/info/exclude&lt;/code&gt; for every single session. That’s a great way to end up with a massive, messy exclude file if you’re not careful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Reviews
&lt;/h2&gt;

&lt;p&gt;Reviewing is where I get to see how others are thinking about the same problems. I did 2 reviews this week:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;py-libp2p&lt;/strong&gt;: I requested changes on &lt;a href="https://github.com/libp2p/py-libp2p/pull/1139" rel="noopener noreferrer"&gt;PR #1139&lt;/a&gt;, which adds Gossipsub comparison and standalone examples. Gossipsub is complex, and while examples are great, they need to be rock-solid so they don't mislead new users.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;agent-orchestrator&lt;/strong&gt;: I approved &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/5137" rel="noopener noreferrer"&gt;PR #5137&lt;/a&gt;, which adds support for Arch Linux and—thankfully—stops self-updates on system installs. System-managed packages should be handled by the package manager, not the app itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;It was a Python-heavy week (6 commits/PRs) with a solid side of Go (2 commits/PRs). &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt;: My focus here was almost entirely on the networking stack—handling sockets, bytes, and protocols.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt;: Used for the orchestrator work, dealing more with SCM APIs and file system abstractions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With 508 additions and only 41 deletions, this was definitely a "building and fixing" week rather than a "cleaning and refactoring" week. I’m adding new capabilities to the WebRTC stack, and that naturally comes with a higher line count.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The goal for next week is to get the ICE-Lite listener PR (#1532) in &lt;code&gt;py-libp2p&lt;/code&gt; across the finish line. I also want to circle back to those socket leaks in the test suite. It's not the most glamorous work, but seeing a clean, green CI with no resource warnings is a special kind of satisfying. On the orchestrator side, I'll be keeping an eye on the SCM polling fixes to make sure our agent's view of the world stays in sync with GitHub.&lt;/p&gt;

&lt;p&gt;Catch you next week!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>devrel</category>
      <category>discuss</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>The receipt should come from the person who received it</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sun, 06 Sep 2026 17:33:22 +0000</pubDate>
      <link>https://dev.to/yashksaini/the-receipt-should-come-from-the-person-who-received-it-4kog</link>
      <guid>https://dev.to/yashksaini/the-receipt-should-come-from-the-person-who-received-it-4kog</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-09-03"&gt;Weekend Challenge: Generosity Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Every donation platform proves the same single fact: money left your account. The fact you actually care about is the second one, that it reached a person, and the receipt for the first is issued by the organisation holding the money. The thing you're trusting and the thing producing the evidence are the same thing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cairn&lt;/strong&gt; changes who issues the receipt.&lt;/p&gt;

&lt;p&gt;You lock SOL in an escrow no intermediary can open. The only thing that moves it is a signature from the recipient's own wallet, and that transaction carries the hash of a recording where they say, in their own words, what they received. The receipt comes from the person at the end of the chain, and the chain welds it to the transfer.&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%2Fbvypf89ux881gr37ibyc.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%2Fbvypf89ux881gr37ibyc.png" alt="Cairn — proof of arrival for charitable giving" width="799" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three commands, and the third one is the whole project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cairn give     &lt;span class="nt"&gt;--to&lt;/span&gt; &amp;lt;them&amp;gt; &lt;span class="nt"&gt;--amount&lt;/span&gt; 0.05 &lt;span class="nt"&gt;--for&lt;/span&gt; &lt;span class="s2"&gt;"one term of school fees"&lt;/span&gt;
cairn receive  &lt;span class="nt"&gt;--escrow&lt;/span&gt; &amp;lt;pubkey&amp;gt; &lt;span class="nt"&gt;--audio&lt;/span&gt; receipt.wav   &lt;span class="c"&gt;# they run this&lt;/span&gt;
cairn verify   &amp;lt;signature&amp;gt;                             &lt;span class="c"&gt;# anyone runs this&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;cairn verify&lt;/code&gt; re-downloads the recording, re-hashes the bytes it gets back, rebuilds the receipt from scratch and compares the result against what's on chain. It doesn't ask my server to be honest. With &lt;code&gt;cairn hash&lt;/code&gt; it doesn't need my server to exist.&lt;/p&gt;

&lt;p&gt;A cairn is a stack of stones travellers raise along a route. Each person who passes adds one. It proves somebody came through, it guides whoever comes next, and it works with nobody owning it and nobody supervising it. Every clause maps onto something the software does, which is the only reason the name stuck.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/yashksaini-coder/Cairn/blob/main/docs/COMMANDS.md" rel="noopener noreferrer"&gt;docs/COMMANDS.md&lt;/a&gt;&lt;/strong&gt; walks the whole loop with real captured terminal output at every step — funding an escrow, recording a receipt, releasing, and verifying.&lt;/p&gt;

&lt;p&gt;The command that matters:&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%2Fiundtjm6idmqf6pcxb4d.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%2Fiundtjm6idmqf6pcxb4d.png" alt="cairn verify, passing" width="799" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Those two hashes were produced by different code paths, minutes apart. The same command against a recording whose bytes have been altered:&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%2Fz79iebooscqoaah2msdq.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%2Fz79iebooscqoaah2msdq.png" alt="cairn verify, mismatch" width="799" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two visibly different hashes and a non-zero exit code, so it can gate a script.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&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/yashksaini-coder" rel="noopener noreferrer"&gt;
        yashksaini-coder
      &lt;/a&gt; / &lt;a href="https://github.com/yashksaini-coder/Cairn" rel="noopener noreferrer"&gt;
        Cairn
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/yashksaini-coder/Cairn/assets/Banner.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%2Fyashksaini-coder%2FCairn%2FHEAD%2Fassets%2FBanner.png" alt="Cairn — proof of arrival for charitable giving" width="100%"&gt;&lt;/a&gt;
&lt;/div&gt;



&lt;p&gt;A weekend build. Lock some SOL for someone; they only get it by signing a
transaction that carries the hash of a voice recording saying what they
received.&lt;/p&gt;
&lt;p&gt;Devnet. Not audited. About 4,500 lines of Rust, of which 400 are the test
suite, and no frontend at all.&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;cairn give     --to &lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;them&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; --amount 0.05 --for &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;one term of school fees&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
cairn receive  --escrow &lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;pubkey&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt; --audio receipt.wav     &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; they run this&lt;/span&gt;
cairn verify   &lt;span class="pl-k"&gt;&amp;lt;&lt;/span&gt;signature&lt;span class="pl-k"&gt;&amp;gt;&lt;/span&gt;                               &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; anyone runs this&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/yashksaini-coder/Cairn/assets/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fyashksaini-coder%2FCairn%2FHEAD%2Fassets%2Fdemo.gif" alt="A terminal session: funds are locked against a description, the recipient records a receipt and releases them, verification passes, the stored recording is altered, and verification then fails." width="100%"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;p&gt;A real session against a local validator — the escrow is created on chain, the
receipt is signed by the recipient's key, and the failure at the end is a
genuine hash mismatch after the stored recording is altered. Also available as
&lt;a href="https://github.com/yashksaini-coder/Cairn/assets/demo.mp4" rel="noopener noreferrer"&gt;mp4&lt;/a&gt;, or replay it yourself with
&lt;code&gt;asciinema play assets/demo.cast&lt;/code&gt;.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The itch&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Donation platforms verify that money left you. They don't verify…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/yashksaini-coder/Cairn" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;A weekend build: roughly 4,500 lines of Rust, 400 of them the test suite. MIT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cairn/
├── programs/cairn/      custody, authorization, state machine
├── crates/
│   ├── cairn-core/      canonical receipt, hashing, seeds, account layout
│   ├── cairn-api/       blob store, index, verifier. Holds no keys.
│   └── cairn-cli/       the client. All of it.
└── docs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The axis everything turns on
&lt;/h3&gt;

&lt;p&gt;Two things here look similar and are not, and the entire design is the consequence of separating them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authorization&lt;/strong&gt; is an ed25519 signature from the recipient key the donor names at creation. The program enforces it. No server key, operator key or admin override substitutes for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testimony&lt;/strong&gt; is the recording. Human evidence, hashed and committed on chain so it's tamper-evident and bound to exactly one transaction signature.&lt;/p&gt;

&lt;p&gt;The recording authorizes nothing. Audio used as an authentication factor is walked through by replay or a decent TTS model, so Cairn treats it as attached evidence with guaranteed integrity instead. That's a much smaller claim, and it's one the software can actually keep.&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%2F3g5kb4k2njlt0tssa3n6.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%2F3g5kb4k2njlt0tssa3n6.png" alt="Cairn architecture" width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the asymmetry. The recipient's way out is a signature; the donor's way back is a deadline. There's no third arrow and nobody standing in the middle.&lt;/p&gt;

&lt;h3&gt;
  
  
  One hash, and everything checks against it
&lt;/h3&gt;

&lt;p&gt;Strip Cairn down and a single sentence is left: two independently computed hashes agree. Everything below exists to make that sentence hard to fake and easy to check.&lt;/p&gt;

&lt;p&gt;It starts with the encoding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;receipt_hash = sha256(
    b"cairn:receipt:v1" ‖ escrow(32) ‖ audio_sha256(32) ‖ transcript ‖ locale(8) ‖ recorded_at(8)
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;locale&lt;/code&gt; is a fixed 8 bytes rather than a string, and that's the load-bearing choice. Every field except the transcript is fixed width, so for a preimage of length &lt;code&gt;L&lt;/code&gt; the transcript is always exactly &lt;code&gt;L - 96&lt;/code&gt; bytes. The encoding is injective with no length prefixes anywhere: no two distinct receipts concatenate to identical bytes. Add a second variable-length field and that evaporates — &lt;code&gt;("ab", "c")&lt;/code&gt; and &lt;code&gt;("a", "bc")&lt;/code&gt; land on the same preimage, and two different receipts share one hash. A test named &lt;code&gt;transcript_and_locale_do_not_smear&lt;/code&gt; exists purely to fail if someone later decides &lt;code&gt;locale&lt;/code&gt; should be a &lt;code&gt;String&lt;/code&gt;. Transcripts are NFC-normalised, trimmed and whitespace-collapsed first, so the same recording hashes the same regardless of which machine composed the receipt.&lt;/p&gt;

&lt;p&gt;Then there's the question of how many implementations of that function exist, and the answer is one. &lt;code&gt;cairn-core&lt;/code&gt; compiles to both &lt;code&gt;sbf-solana-solana&lt;/code&gt; and the host, so the on-chain program, the CLI and the API server all call the same &lt;code&gt;receipt_hash()&lt;/code&gt;. Not kept in sync, not mirrored, not generated from a shared schema — the same function, compiled twice. The account layout works the same way: &lt;code&gt;Escrow&lt;/code&gt; is defined once in &lt;code&gt;cairn-core&lt;/code&gt; and the program wraps it in a newtype that Anchor's traits hang off, so the bytes the program writes are the bytes the indexer reads, with a test asserting that Anchor's generated discriminator matches the constant the decoder uses.&lt;/p&gt;

&lt;p&gt;That count is also why the client is a CLI. A browser would need its own &lt;code&gt;receipt_hash()&lt;/code&gt;, which means hand-porting the normalisation into TypeScript, and JavaScript's &lt;code&gt;\s&lt;/code&gt; and Rust's &lt;code&gt;char::is_whitespace&lt;/code&gt; disagree about U+FEFF — a byte-order mark in a transcript gives you a browser reporting a valid receipt and a chain reporting a fake one. That character is patchable; the shape of it isn't. Any second implementation is a second answer, and an interface whose job is to display "these match" while running different code from the thing doing the matching argues against itself.&lt;/p&gt;

&lt;p&gt;With one function to check against, three commands do the checking rather than taking anything on faith. &lt;code&gt;cairn show&lt;/code&gt; re-hashes the description client-side against the on-chain &lt;code&gt;need_hash&lt;/code&gt;, so the API can serve whatever text it likes and it doesn't matter. &lt;code&gt;cairn receive&lt;/code&gt; rebuilds the receipt from the audio on the recipient's own disk and refuses to sign if the server's hash doesn't cover it. &lt;code&gt;cairn hash&lt;/code&gt; touches no network, needs no key and reads no database — compute a hash with the wifi off and compare it against a block explorer.&lt;/p&gt;

&lt;p&gt;Which leaves the server holding nothing worth stealing: no keypair, no signing path, no instruction it could call to move a lamport.&lt;/p&gt;

&lt;h3&gt;
  
  
  Speech, and why the transcript is a convenience
&lt;/h3&gt;

&lt;p&gt;Transcription runs through &lt;a href="https://elevenlabs.io/" rel="noopener noreferrer"&gt;ElevenLabs Scribe&lt;/a&gt;. The recipient speaks whatever language they actually speak, and the transcript joins the canonical receipt alongside the locale tag.&lt;/p&gt;

&lt;p&gt;It's wired as best-effort enrichment rather than a dependency. The hash covers the audio bytes either way, so a receipt is valid with or without a transcript — the audio is the evidence, and the transcript is a convenience for whoever reads it later. Depending on a third party that lightly felt like the right call inside something whose whole argument is about not needing to trust third parties.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Cairn proves
&lt;/h3&gt;

&lt;p&gt;One sentence, precisely: that a specific recording, unaltered, was committed by the holder of a specific key in the same instruction that moved specific funds. There's no window in which the money has moved without a receipt, or a receipt exists without the money.&lt;/p&gt;

&lt;p&gt;Every design decision above exists to make that sentence checkable by a stranger who has no access to me, my server, or my database. Every state transition has a happy-path test in LiteSVM, and every guard has a negative test asserting its specific error code. The one that carries the trust model is &lt;code&gt;only_the_named_recipient_can_release&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best Use of Solana.&lt;/strong&gt; The escrow program isn't a payment rail bolted onto the side of the idea — it's the only reason the idea works. A receipt that anyone with database access can remove is a receipt back under the control of the party you were trying not to have to trust, which is the exact problem the project starts from. Putting it on Solana moves custody into a program-owned vault PDA with deterministic seeds and no operator key, makes the recipient's ed25519 signature the sole authorization primitive, and makes the receipt append-only by construction rather than by policy. The program is the trust model. Everything off-chain is a convenience wrapped around it, and it says so by holding no keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best Use of ElevenLabs.&lt;/strong&gt; &lt;a href="https://elevenlabs.io/" rel="noopener noreferrer"&gt;Scribe&lt;/a&gt; closes the language gap that sits between a donor and a recipient by default. The audio is the evidence, but audio in a language you don't read is evidence you can't check, and a transcript makes it legible to someone on the other side of that gap. The locale tag travels inside the hash preimage alongside the transcript, so the language a receipt was given in is part of what gets committed — not metadata bolted on afterwards, but a field the signature covers.&lt;/p&gt;

&lt;p&gt;It was a good challenge that stretched me to make use of solana and AI together in a interesting manner. Interesting part was the idea of getting the scribe using eleven labs. Overall a good exercise to learn something new. &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Yash K Saini&lt;/strong&gt; — Engineer, building in public — AI/ML, low-level (Rust/C/C++), and open source.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>ai</category>
      <category>rust</category>
    </item>
    <item>
      <title>Dev log #20 Deleting 180k lines and chasing socket leaks: A week in the OSS trenches</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Sun, 06 Sep 2026 13:46:16 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-deleting-180k-lines-and-chasing-socket-leaks-a-week-in-the-oss-trenches-4f9b</link>
      <guid>https://dev.to/yashksaini/dev-log-deleting-180k-lines-and-chasing-socket-leaks-a-week-in-the-oss-trenches-4f9b</guid>
      <description>&lt;p&gt;&lt;strong&gt;Yash K Saini&lt;/strong&gt; — Engineer, building in public — AI/ML, low-level (Rust/C/C++), and open source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A massive week of cleanup and deep technical dives. From deleting 180k lines in my vault to fixing socket leaks in libp2p and contributing to the Rust compiler, I hit 102 commits and a perfect 7-day streak.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;There is something incredibly therapeutic about a net-negative week. I pushed 102 commits across a dozen repos, but the headline stat isn't the additions—it’s the 188,000 lines of code I deleted. Between chasing down residual socket leaks in &lt;code&gt;py-libp2p&lt;/code&gt;, building out a resumable progress log for &lt;code&gt;Reachable&lt;/code&gt;, and even opening a PR on the Rust compiler, it’s been a high-velocity seven days.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Great Purge and Portfolio Polish
&lt;/h2&gt;

&lt;p&gt;I started the week with a "scorched earth" approach to my &lt;code&gt;obsidian-vault&lt;/code&gt;. I realized I was tracking way too much of Obsidian's internal UI state and plugin noise, which made the git history a mess. I ripped out about 179,000 lines by stopping the tracking of that ephemeral state. Now, my syncs are clean, and the vault actually represents my thoughts rather than my editor's window positions. It’s a good reminder that just because you &lt;em&gt;can&lt;/em&gt; version control something doesn't mean you &lt;em&gt;should&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;On the &lt;a href="https://github.com/yashksaini-coder/portfolio" rel="noopener noreferrer"&gt;portfolio&lt;/a&gt; front, I spent a good chunk of time in &lt;code&gt;app/projects&lt;/code&gt; and &lt;code&gt;components/common&lt;/code&gt;. I finally stripped out some old "catalogue-numbering" chrome that was cluttering the UI and gave each project row its own distinct card surface. I also fixed a nagging SEO issue by moving the canonical origin to &lt;code&gt;yashksaini.vercel.app&lt;/code&gt;. The big feature update here was the new "spotlight" section, where I’m now featuring &lt;a href="https://github.com/yashksaini-coder/GitBanner" rel="noopener noreferrer"&gt;GitBanner&lt;/a&gt; and &lt;a href="https://github.com/yashksaini-coder/Reachable" rel="noopener noreferrer"&gt;Reachable&lt;/a&gt; right at the top. If you’re going to build things, you might as well show them off properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep in the Networking Guts
&lt;/h2&gt;

&lt;p&gt;A lot of my mental energy this week went into &lt;a href="https://github.com/libp2p/py-libp2p" rel="noopener noreferrer"&gt;py-libp2p&lt;/a&gt;. Networking bugs are the ultimate test of patience. I opened two pretty critical issues regarding socket leaks. In &lt;a href="https://github.com/libp2p/py-libp2p/issues/1485" rel="noopener noreferrer"&gt;Issue #1485&lt;/a&gt;, I found that the Swarm was leaking dialed connection sockets on shutdown because the service-manager stop path wasn't cleaning up properly. I followed that up with &lt;a href="https://github.com/libp2p/py-libp2p/issues/1487" rel="noopener noreferrer"&gt;Issue #1487&lt;/a&gt;, which identified residual leaks in the muxer connection test fixtures.&lt;/p&gt;

&lt;p&gt;I didn't just report them, though. I’ve been working on &lt;a href="https://github.com/libp2p/py-libp2p/pull/1486" rel="noopener noreferrer"&gt;PR #1486&lt;/a&gt; to ensure connections are explicitly closed on shutdown so their sockets are released back to the OS. I also pushed forward on &lt;a href="https://github.com/libp2p/py-libp2p/pull/1471" rel="noopener noreferrer"&gt;WebRTC-Direct interop tests&lt;/a&gt;. Getting Python and Go to agree on WebRTC-Direct is... a journey. We had a case where the ICE handshake succeeded, but then the connection was immediately torn down. It’s these kinds of edge cases that make p2p work both frustrating and deeply rewarding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building and Fixing at Scale
&lt;/h2&gt;

&lt;p&gt;I also spent some time on &lt;a href="https://github.com/xevrion/breakscale" rel="noopener noreferrer"&gt;breakscale&lt;/a&gt;, which is a project that really challenges how I think about failure states. I found a bug (Issue #51) where a node losing 25% of its requests to timeouts was still reporting a 0% failure rate. That’s a dangerous blind spot. I merged &lt;a href="https://github.com/xevrion/breakscale/pull/57" rel="noopener noreferrer"&gt;PR #57&lt;/a&gt; to ensure timeouts are counted correctly so the failure rate actually reflects reality. I also fixed a nasty data loss bug in &lt;a href="https://github.com/xevrion/breakscale/pull/54" rel="noopener noreferrer"&gt;PR #54&lt;/a&gt;—previously, if the validator rejected a saved design, the next save or delete would just erase it from storage. Now, we keep the design even if the current build can't "open" it.&lt;/p&gt;

&lt;p&gt;Over at &lt;a href="https://github.com/yashksaini-coder/Reachable" rel="noopener noreferrer"&gt;Reachable&lt;/a&gt;, the focus was on the demo experience. I built out a resumable progress log, a sound score mixer, and a frame renderer. This project is becoming a really cool playground for TypeScript-based media tools. I’m particularly happy with the "paste-ready" voiceover script block—it’s one of those small DX (developer experience) features that makes the tool feel much more polished.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tooling Life: Neovim and DevNotion
&lt;/h2&gt;

&lt;p&gt;You can’t be a dev without constantly tweaking your tools. I did some work on my &lt;a href="https://github.com/yashksaini-coder/nvim" rel="noopener noreferrer"&gt;nvim&lt;/a&gt; config, specifically rebuilding the keymap reference around a prefix tree. I also fixed a frustrating bug where &lt;code&gt;&amp;lt;leader&amp;gt;bd&lt;/code&gt; would leave a blank window open. Now it closes the window properly, and I can open a terminal from any window type, not just "normal" ones. It’s the little things that keep the flow state alive.&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;, I focused on the documentation and the "blog log." I reworked the masthead and made the ledger header sticky while scrolling. I also made a conscious decision to drop the sequential numbering from posts. Now they’re just titled "Dev log: ." It feels less like a chore and more like a journal. I also had to escape some pipes in the headlines to prevent the markdown tables in the logs from breaking—classic string manipulation fun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests of Note
&lt;/h2&gt;

&lt;p&gt;Beyond the ones I've mentioned, a few others stood out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Rust Compiler&lt;/strong&gt;: I opened &lt;a href="https://github.com/rust-lang/rust/pull/162123" rel="noopener noreferrer"&gt;PR #162123&lt;/a&gt; to report unsatisfied opaque item bounds instead of a generic type mismatch under the &lt;code&gt;-Znext-solver&lt;/code&gt;. Contributing to the Rust compiler is always a "hold your breath" moment, but it's the best way to learn how the magic happens.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Agent Orchestrator&lt;/strong&gt;: Merged a fix in &lt;a href="https://github.com/Untrivial-ai/agent-orchestrator/pull/4717" rel="noopener noreferrer"&gt;PR #4717&lt;/a&gt; to carry the resolved model through metadata merging. This fixed a bug where &lt;code&gt;ao spawn&lt;/code&gt; would record an empty model.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Sixb&lt;/strong&gt;: Merged &lt;a href="https://github.com/sixb-ai/sixb/pull/478" rel="noopener noreferrer"&gt;PR #478&lt;/a&gt; which adds the project model catalog to both the core and server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tech Stack &amp;amp; Vibe
&lt;/h2&gt;

&lt;p&gt;This was a true polyglot week. TypeScript dominated the frontend and CLI work, Python was the heavy hitter for the libp2p networking logic, and I dipped into Rust for the compiler work. I even wrote a bit of C++ for some basic alphabet-printing programs in my &lt;a href="https://github.com/yashksaini-coder/Learning-2026" rel="noopener noreferrer"&gt;Learning-2026&lt;/a&gt; repo—sometimes you just need to get back to the basics to clear your head.&lt;/p&gt;

&lt;p&gt;The 7-day streak feels good. It wasn't a forced grind; it was just one of those weeks where every time I sat down, there was a clear problem to solve. Whether it was a socket leak in Python or a CSS alignment issue in my portfolio, the momentum stayed high.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next week is going to be about closing the loop on those &lt;code&gt;py-libp2p&lt;/code&gt; leaks. I want to get the connection-closing logic merged and see if I can figure out why the Go and Python WebRTC-Direct implementations are talking past each other. I’ll also be spending more time on the &lt;code&gt;Reachable&lt;/code&gt; frame renderer—there’s some performance optimization I want to try out.&lt;/p&gt;

&lt;p&gt;Catch you in the next push.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>rust</category>
      <category>networking</category>
    </item>
    <item>
      <title>Dev log #19 WebRTC v2 flows, agentic orchestration, and a perfectly synced vault</title>
      <dc:creator>Yash Kumar Saini</dc:creator>
      <pubDate>Mon, 31 Aug 2026 06:52:21 +0000</pubDate>
      <link>https://dev.to/yashksaini/dev-log-19-webrtc-v2-flows-agentic-orchestration-and-a-perfectly-synced-vault-5egd</link>
      <guid>https://dev.to/yashksaini/dev-log-19-webrtc-v2-flows-agentic-orchestration-and-a-perfectly-synced-vault-5egd</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This week was a high-output sprint across the stack—from low-level p2p networking in Python to refining agentic workflows in TypeScript. I pushed 53 commits and opened 13 PRs, maintaining a perfect 7-day streak while balancing deep protocol work with personal knowledge management.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I didn't really intend for this to be a "build everything" week, but that’s exactly where the momentum took me. Between hardening WebRTC implementations in &lt;code&gt;py-libp2p&lt;/code&gt; and chasing down edge cases in agent orchestration, I managed to ship 53 commits and keep my daily streak alive for the full seven days. The stats show a heavy tilt toward new code—over 12,000 additions—as I laid the groundwork for better telemetry and more robust p2p networking.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Deep in the Networking weeds: py-libp2p
&lt;/h3&gt;

&lt;p&gt;Most of my "deep work" hours went into &lt;code&gt;py-libp2p&lt;/code&gt;. Networking code is unforgiving, but incredibly satisfying when it clicks. I spent a significant chunk of time in &lt;code&gt;libp2p/kad_dht&lt;/code&gt; implementing configurable subnet-diversity limits and table-wide IP-group caps. If you've ever dealt with Sybil attacks or just messy peer distributions, you know why this matters—it’s about making the DHT resilient, not just functional.&lt;/p&gt;

&lt;p&gt;On the transport side, I was neck-deep in &lt;code&gt;libp2p/transport&lt;/code&gt; fixing WebRTC issues. I had to guard private-slot writes and ensure we’re using &lt;code&gt;close_peer_connection&lt;/code&gt; at every production site to avoid hanging resources. I also spent time on a tricky Windows-specific bug where we needed to listen on a concrete non-loopback interface for tests to actually pass. &lt;/p&gt;

&lt;h3&gt;
  
  
  Hardening Reachable &amp;amp; Breakscale
&lt;/h3&gt;

&lt;p&gt;I’ve been refining &lt;strong&gt;Reachable&lt;/strong&gt;, specifically making the "Ask" feature behave more like a natural conversation. I had to harden the store behind it to ensure state doesn't drift when the UI gets complex. &lt;/p&gt;

&lt;p&gt;Over at &lt;strong&gt;breakscale&lt;/strong&gt;, I hit a weird one: Vitest failing on Node 26 because of &lt;code&gt;jsdom&lt;/code&gt;'s &lt;code&gt;localStorage&lt;/code&gt; implementation. I opened an issue, tracked it down, and pushed a fix to put &lt;code&gt;localStorage&lt;/code&gt; back where it belongs. I also shipped a feature to improve design descriptions—focusing on what a design is &lt;em&gt;made of&lt;/em&gt; rather than just its dimensions. It’s a small UX shift, but it makes the tool feel much more intuitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Digital Brain: obsidian-vault
&lt;/h3&gt;

&lt;p&gt;I treat my &lt;code&gt;obsidian-vault&lt;/code&gt; like a production repo, and this week it saw 35 commits. I was mostly syncing UI states and fixing templates. I noticed a bug in my "Concept" template where the parent pointer was recursively pointing at the template itself (classic meta-mess). I also logged three separate stream sessions, which has been a great way to document my thought process while I code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull Requests &amp;amp; Orchestration
&lt;/h2&gt;

&lt;p&gt;I opened 13 PRs this week, and while some are still in flight, they represent a lot of the "glue" work I do across the ecosystem. &lt;/p&gt;

&lt;p&gt;A lot of my focus was on &lt;code&gt;agent-orchestrator&lt;/code&gt;. I’m working on carrying request IDs into daemon-emitted events to make telemetry actually useful. There’s nothing worse than seeing an error in a log and having no way to trace it back to the original trigger. I also fixed a dev-server proxy issue where it was pointing at the wrong daemon—one of those "how did this ever work?" bugs that felt great to squash.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;corsair&lt;/code&gt; repo, I’ve been on a mission to improve reliability and performance. I opened PRs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Cover &lt;code&gt;verifyHmacSignature&lt;/code&gt; with proper unit tests (security code without tests is just a disaster waiting to happen).&lt;/li&gt;
&lt;li&gt;  Add database indexes on &lt;code&gt;corsair_entities&lt;/code&gt; and &lt;code&gt;corsair_accounts&lt;/code&gt; query paths. As the data grows, those sequential scans were starting to hurt.&lt;/li&gt;
&lt;li&gt;  Clean up CLI error handling so async command failures don't just dump unhandled promise rejections into the terminal.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Issues &amp;amp; Community
&lt;/h2&gt;

&lt;p&gt;I opened 7 issues this week, mostly as a way to "work in the open." When I find a bug in &lt;code&gt;agent-orchestrator&lt;/code&gt; (like the Connect Mobile tailnet proxy stranding processes on ungraceful exits) or a performance bottleneck in &lt;code&gt;corsair&lt;/code&gt;, I document it before I fix it. It helps me stay organized and gives the maintainers a heads-up on what I’m poking at.&lt;/p&gt;

&lt;p&gt;One specific bug I’m still tracking is in &lt;code&gt;omarchy&lt;/code&gt;, where selecting the default agent somehow triggers a version downgrade because of a &lt;code&gt;mise&lt;/code&gt; release cooldown. It’s a weird interaction between the version manager and the agent logic that I’m hoping to resolve next week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack &amp;amp; Vibe
&lt;/h2&gt;

&lt;p&gt;This was a polyglot week, but Python and TypeScript were the clear winners.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Python:&lt;/strong&gt; 84MB of code touched (mostly libp2p internals).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;TypeScript:&lt;/strong&gt; 58MB of code across frontend tools and CLI utilities.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Mix:&lt;/strong&gt; A bit of Lua for my &lt;code&gt;nvim&lt;/code&gt; config updates and some Shell/Makefile glue to keep the CI pipelines happy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With 12,205 additions against 1,500 deletions, this was definitely a "growth" week. I wasn't just cleaning up; I was shipping new features and expanding test suites. The 7-day streak wasn't forced—when you're bouncing between p2p protocols and AI agents, there's always one more thing you want to tweak before calling it a day.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next week, I’m planning to close out those WebRTC v2 PRs in &lt;code&gt;py-libp2p&lt;/code&gt;. The specs are solid, but the implementation details around data-channel framing as a byte stream are still a bit finicky. I also want to get those &lt;code&gt;corsair&lt;/code&gt; database indexes merged and see the real-world performance delta.&lt;/p&gt;

&lt;p&gt;And of course, more streaming. I’ve found that logging my sessions in Obsidian has made me much more intentional about the code I write. If I have to explain it to a "future me" in a log, I'm less likely to take the lazy shortcut.&lt;/p&gt;

&lt;p&gt;Catch you in the next push. Keep hacking.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Yash K Saini&lt;/strong&gt; — Engineer, building in public — AI/ML, low-level (Rust/C/C++), and open source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/yashksaini-coder" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://x.com/0xcrackedDev" rel="noopener noreferrer"&gt;X&lt;/a&gt; · &lt;a href="https://www.linkedin.com/in/yashksaini" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="https://yashksaini.vercel.app/" rel="noopener noreferrer"&gt;Portfolio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Generated by &lt;a href="https://github.com/yashksaini-coder/DevNotion" rel="noopener noreferrer"&gt;DevNotion&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>ai</category>
      <category>programming</category>
      <category>python</category>
    </item>
  </channel>
</rss>
