<?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: Arun Soman</title>
    <description>The latest articles on DEV Community by Arun Soman (@aarun_soman_).</description>
    <link>https://dev.to/aarun_soman_</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%2F4090021%2F9e3f0ec2-6267-4b8a-9ea3-a0667ea94260.jpg</url>
      <title>DEV Community: Arun Soman</title>
      <link>https://dev.to/aarun_soman_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aarun_soman_"/>
    <language>en</language>
    <item>
      <title>Idiomatic Concurrency in Rust: Stop Writing Accidental Microservices</title>
      <dc:creator>Arun Soman</dc:creator>
      <pubDate>Fri, 25 Sep 2026 14:44:36 +0000</pubDate>
      <link>https://dev.to/aarun_soman_/idiomatic-concurrency-in-rust-stop-writing-accidental-microservices-dmf</link>
      <guid>https://dev.to/aarun_soman_/idiomatic-concurrency-in-rust-stop-writing-accidental-microservices-dmf</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;A common pattern in Rust async applications looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the Tokio runtime.&lt;/li&gt;
&lt;li&gt;Break the app into small “microservices” that each run in their own task.&lt;/li&gt;
&lt;li&gt;Give each service a &lt;code&gt;run&lt;/code&gt; function that loops over a &lt;code&gt;tokio::select!&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add a branch for &lt;code&gt;CancellationToken::cancelled()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Wire the root token to &lt;code&gt;ctrl_c&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Let services communicate through channels: &lt;code&gt;mpsc&lt;/code&gt;, &lt;code&gt;oneshot&lt;/code&gt;, &lt;code&gt;watch&lt;/code&gt;, &lt;code&gt;broadcast&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When one service fails, bubble the error up and let the whole application fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first this feels clean. Each component is isolated. Each has a lifecycle. Each has a cancellation path.&lt;/p&gt;

&lt;p&gt;But then the bugs start.&lt;/p&gt;

&lt;p&gt;One service returns &lt;code&gt;Err&lt;/code&gt;. Nothing cancels its siblings. The sibling is still waiting on a channel receive. The sender is gone. The receiver parks forever. The app does not crash. It does not shut down. It just hangs.&lt;/p&gt;

&lt;p&gt;The developer adds more &lt;code&gt;select!&lt;/code&gt; branches. More cancellation checks. More boilerplate. The same failure mode appears somewhere else.&lt;/p&gt;

&lt;p&gt;The real issue is not Tokio. The real issue is lifecycle ownership.&lt;/p&gt;

&lt;p&gt;This is not microservices. It is a modular monolith with in-process tasks. That label matters because it changes the failure model. There is no network partition. There is no independent fate. There is shared fate. If one component dies, the process is still alive, and the rest of the system may be stuck waiting for it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tokio::spawn&lt;/code&gt; detaches. Dropping a &lt;code&gt;JoinHandle&lt;/code&gt; does not cancel the task. A failed or panicked task does not automatically cancel its siblings. A &lt;code&gt;select!&lt;/code&gt; inside one component does not fix global lifecycle semantics.&lt;/p&gt;

&lt;p&gt;The result is exactly the pain described: redundant code, unhandled paths, stopped services, and other services waiting forever for a response that will never come.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;Treat the application as a &lt;strong&gt;supervised set of in-process tasks in a monolith&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Own task lifetimes. Define failure policy. Make every long-lived await cancel-aware. Keep simple things simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Use structured concurrency
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;JoinSet&lt;/code&gt; to own child tasks. Use &lt;code&gt;TaskTracker&lt;/code&gt; if you need to track graceful shutdown completion.&lt;/p&gt;

&lt;p&gt;Do not detach tasks unless you truly mean fire-and-forget.&lt;/p&gt;

&lt;p&gt;Rust and Tokio do not have built-in structured concurrency. &lt;code&gt;JoinSet&lt;/code&gt; and &lt;code&gt;TaskTracker&lt;/code&gt; approximate it. &lt;code&gt;TaskTracker&lt;/code&gt; is for &lt;code&gt;close()&lt;/code&gt; + &lt;code&gt;wait()&lt;/code&gt; — “everyone is done.” It does not propagate failure. Pair it with &lt;code&gt;JoinSet&lt;/code&gt; when you care why things ended.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Use one root cancellation token
&lt;/h3&gt;

&lt;p&gt;Create one root &lt;code&gt;CancellationToken&lt;/code&gt; from &lt;code&gt;ctrl_c&lt;/code&gt;. Give each component a child token.&lt;/p&gt;

&lt;p&gt;Child tokens propagate parent → child only. A failing child does &lt;strong&gt;not&lt;/strong&gt; cancel the root. The supervisor must observe the failure via &lt;code&gt;join_next&lt;/code&gt; and call &lt;code&gt;root.cancel()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Decide failure policy per component
&lt;/h3&gt;

&lt;p&gt;Fail-fast? Restart with backoff? Degrade? Shut down cleanly?&lt;/p&gt;

&lt;p&gt;Only restart if the component is safe to restart. Otherwise, bubble the error up and shut down the whole application deliberately.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Make every long-lived await cancel-aware
&lt;/h3&gt;

&lt;p&gt;This is the actual fix for “nobody responding.”&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CancellationToken&lt;/code&gt; does not wake a task parked in &lt;code&gt;rx.recv().await&lt;/code&gt;. If a service is blocked on a channel receive that is not racing the token, the drain loop hangs forever.&lt;/p&gt;

&lt;p&gt;Every long-lived await must either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;select!&lt;/code&gt; on &lt;code&gt;token.cancelled()&lt;/code&gt;, or&lt;/li&gt;
&lt;li&gt;rely on sender-drop so &lt;code&gt;recv()&lt;/code&gt; returns &lt;code&gt;Err&lt;/code&gt; / &lt;code&gt;None&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Owners should drop their senders during shutdown.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Use the right channel for the job
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;watch&lt;/code&gt; for state/config&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;broadcast&lt;/code&gt; for events&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mpsc&lt;/code&gt; for commands&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;oneshot&lt;/code&gt; for replies&lt;/li&gt;
&lt;li&gt;bounded channels for backpressure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unbounded channels hide backpressure bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Do not make everything a task
&lt;/h3&gt;

&lt;p&gt;Only spawn when you need independent lifetime, async I/O, or state isolation. If it is just a function call, call it. Message passing everywhere makes simple things harder.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Know your runtime defaults
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;#[tokio::main]&lt;/code&gt; is multi-threaded by default. &lt;code&gt;worker_threads&lt;/code&gt; defaults to the number of CPUs. &lt;code&gt;#[tokio::test]&lt;/code&gt; is current-thread by default.&lt;/p&gt;

&lt;p&gt;More worker threads do not make blocking calls safe. Use &lt;code&gt;spawn_blocking&lt;/code&gt; for blocking work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Shape
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&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;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;task&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;JoinSet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tokio_util&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;service_a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Every long-lived await should be cancel-aware.&lt;/span&gt;
    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;select!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="nf"&gt;.cancelled&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;Ok&lt;/span&gt;&lt;span class="p"&gt;(()),&lt;/span&gt;
            &lt;span class="c1"&gt;// _ = rx.recv() =&amp;gt; { ... }&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;service_b&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;loop&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;select!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="nf"&gt;.cancelled&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;Ok&lt;/span&gt;&lt;span class="p"&gt;(()),&lt;/span&gt;
            &lt;span class="c1"&gt;// _ = rx.recv() =&amp;gt; { ... }&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&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="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&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="o"&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;shutdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CancellationToken&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="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;JoinSet&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="n"&gt;tasks&lt;/span&gt;&lt;span class="nf"&gt;.spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;service_a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shutdown&lt;/span&gt;&lt;span class="nf"&gt;.child_token&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="nf"&gt;.spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;service_b&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shutdown&lt;/span&gt;&lt;span class="nf"&gt;.child_token&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;first_error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nn"&gt;anyhow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;select!&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ctrl_c&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="nf"&gt;.join_next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;Some&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="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())))&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// Normal exit. For a forever-service this is unexpected.&lt;/span&gt;
                    &lt;span class="c1"&gt;// For finite work, you may want to continue instead of cancelling.&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nf"&gt;Some&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="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="n"&gt;first_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&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="nf"&gt;Some&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;join_err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;first_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;join_err&lt;/span&gt;&lt;span class="nf"&gt;.into&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
                &lt;span class="nb"&gt;None&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;shutdown&lt;/span&gt;&lt;span class="nf"&gt;.cancel&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Drain remaining tasks. Don't use `?` here; collect/log instead.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;drain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="nf"&gt;.join_next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;res&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="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(()))&lt;/span&gt; &lt;span class="k"&gt;=&amp;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="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="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first_error&lt;/span&gt;&lt;span class="nf"&gt;.is_none&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;first_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&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="p"&gt;}&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;join_err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first_error&lt;/span&gt;&lt;span class="nf"&gt;.is_none&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;first_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;join_err&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;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;if&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&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="n"&gt;drain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
        &lt;span class="nf"&gt;.is_err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="nf"&gt;.abort_all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="nf"&gt;.join_next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.is_some&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;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&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="n"&gt;first_error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;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="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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;JoinSet&lt;/code&gt; forces one output type across all spawns. Heterogeneous services need a common error type like &lt;code&gt;anyhow::Error&lt;/code&gt; or a mapped enum.&lt;/li&gt;
&lt;li&gt;After cancellation, a task may legitimately return &lt;code&gt;Err&lt;/code&gt;. Log it instead of double-reporting the first error.&lt;/li&gt;
&lt;li&gt;If you need actor semantics, crates like &lt;code&gt;ractor&lt;/code&gt; exist. Only reach for them if you actually need actors.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rules of Thumb
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rename the mental model: &lt;strong&gt;supervised tasks in a monolith&lt;/strong&gt;, not microservices.&lt;/li&gt;
&lt;li&gt;Own task lifetimes with &lt;code&gt;JoinSet&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use one root &lt;code&gt;CancellationToken&lt;/code&gt; and child tokens.&lt;/li&gt;
&lt;li&gt;Remember: cancellation flows parent → child, not child → parent.&lt;/li&gt;
&lt;li&gt;Make every long-lived await cancel-aware.&lt;/li&gt;
&lt;li&gt;Define failure policy per component.&lt;/li&gt;
&lt;li&gt;Use bounded channels for backpressure.&lt;/li&gt;
&lt;li&gt;Do not spawn everything.&lt;/li&gt;
&lt;li&gt;Keep plain function calls as plain function calls.&lt;/li&gt;
&lt;li&gt;Heartbeats are for separate processes. In-process, await task completion.&lt;/li&gt;
&lt;li&gt;Coroutines will not solve lifecycle or failure semantics.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One-Liner
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Stop writing accidental microservices: treat Tokio tasks as supervised components in a monolith, own their lifetimes with &lt;code&gt;JoinSet&lt;/code&gt;, cancel through a root token, and make every await cancel-aware.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Built a Flight Recorder for AI Agents (Because “What Did It Do?” Shouldn’t Be a Mystery)</title>
      <dc:creator>Arun Soman</dc:creator>
      <pubDate>Fri, 25 Sep 2026 12:49:20 +0000</pubDate>
      <link>https://dev.to/aarun_soman_/i-built-a-flight-recorder-for-ai-agents-because-what-did-it-do-shouldnt-be-a-mystery-235h</link>
      <guid>https://dev.to/aarun_soman_/i-built-a-flight-recorder-for-ai-agents-because-what-did-it-do-shouldnt-be-a-mystery-235h</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;AI agents now run your shell, edit your files, and ship to prod—often with zero humans in the loop. The tooling explosion of 2026 made software &lt;em&gt;agent-native&lt;/em&gt;… and produced exactly one new question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What did my agent actually do while I was away?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/arunsoman/agentbox" rel="noopener noreferrer"&gt;AGENTBOX&lt;/a&gt; is the answer. Strap a flight recorder to &lt;strong&gt;any&lt;/strong&gt; command or agent—no SDK, no code changes, no cloud—and get a tamper-evident tape of everything it did: every tool call, every file touched, every URL hit.&lt;/p&gt;

&lt;p&gt;I recently came across this project and it solves a problem I didn't know I had. Let me break down why it matters and how you can try it in 10 seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Agents Have Root. Who's Watching?
&lt;/h2&gt;

&lt;p&gt;If you've used Claude Code, Codex CLI, or any autonomous coding agent, you know the drill: you give it a task, it runs for a while, and then something breaks. Maybe it deleted a file, maybe it hit an unexpected API, or maybe it just did something… &lt;em&gt;odd&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Traditional logging doesn't cut it. You need a &lt;strong&gt;forensic record&lt;/strong&gt;—a black box that captures everything in a way that can't be quietly edited afterward.&lt;/p&gt;

&lt;p&gt;That's exactly what AGENTBOX does.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is AGENTBOX?
&lt;/h2&gt;

&lt;p&gt;AGENTBOX is a &lt;strong&gt;zero-dependency, 100% local flight recorder for AI agents&lt;/strong&gt;. It wraps any command or agent and produces a session file (JSONL) containing every event: tool calls, shell commands, file operations, URLs hit, even human keystrokes.&lt;/p&gt;

&lt;p&gt;The tagline says it all:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"&lt;code&gt;npm test&lt;/code&gt; for your agent's behavior · tamper-evident · 100% local · zero dependencies"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🎥 The Replay: Security Footage for Your Terminal
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;agentbox replay&lt;/code&gt; opens an interactive scrubber—like security footage for your terminal.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timeline bar&lt;/strong&gt; with markers: &lt;code&gt;▲&lt;/code&gt; tool call · &lt;code&gt;$&lt;/code&gt; shell · &lt;code&gt;✎&lt;/code&gt; file op · &lt;code&gt;i&lt;/code&gt; human input&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Play / pause / speed&lt;/strong&gt; (1×–64×)—skip to &lt;code&gt;00:47.2&lt;/code&gt;, the moment it dropped the table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every keystroke the human typed&lt;/strong&gt; is on the tape (&lt;code&gt;HUMAN&lt;/code&gt; lines)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--headless&lt;/code&gt; renders a static frame for CI, GIFs, and GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔒 The Tape Cannot Lie
&lt;/h3&gt;

&lt;p&gt;Every event is hash-chained. Each line commits to the previous one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;hash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;hash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sha&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="err"&gt;(prev&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‖&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‖&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‖&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;‖&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;data)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit one line—even a single character—and &lt;code&gt;agentbox verify&lt;/code&gt; pins the exact event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;agentbox verify
✗ &lt;span class="nb"&gt;hash &lt;/span&gt;mismatch at event 7 — event was tampered with or forged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes the session file &lt;strong&gt;evidence, not a log&lt;/strong&gt;: post-mortems, compliance, "the agent did it / no it didn't" arguments in PRs—settled.&lt;/p&gt;

&lt;h3&gt;
  
  
  🛡️ Secrets Never Land on the Tape
&lt;/h3&gt;

&lt;p&gt;Every string written to a session file is scrubbed &lt;em&gt;before&lt;/em&gt; it is hashed and appended. The chain commits to the redacted form, so the original secret is not recoverable from the file, the receipt, or a shared clip. Default patterns catch API tokens, cloud keys, auth headers, private keys, and connection strings.&lt;/p&gt;

&lt;p&gt;You can extend the redaction patterns via &lt;code&gt;AGENTBOX_REDACT_EXTRA&lt;/code&gt; or project-local config.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quickstart: See It in 10 Seconds
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Watch a scripted agent get recorded&lt;/span&gt;
npx agentbox-cli demo

&lt;span class="c"&gt;# 2. Wrap anything — your agent, a script, any CLI&lt;/span&gt;
agentbox wrap &lt;span class="nt"&gt;--&lt;/span&gt; claude &lt;span class="s2"&gt;"refactor auth.js"&lt;/span&gt;
agentbox wrap &lt;span class="nt"&gt;--name&lt;/span&gt; eval-run &lt;span class="nt"&gt;--&lt;/span&gt; python evaluate.py &lt;span class="nt"&gt;--suite&lt;/span&gt; prod

&lt;span class="c"&gt;# 3. Read the tape&lt;/span&gt;
agentbox list        &lt;span class="c"&gt;# all sessions&lt;/span&gt;
agentbox receipt     &lt;span class="c"&gt;# newest session, one page&lt;/span&gt;
agentbox replay      &lt;span class="c"&gt;# scrub the footage&lt;/span&gt;
agentbox verify      &lt;span class="c"&gt;# tamper check&lt;/span&gt;

&lt;span class="c"&gt;# 4. Share a moment, not a dump&lt;/span&gt;
agentbox clip &lt;span class="nt"&gt;--from&lt;/span&gt; 30 &lt;span class="nt"&gt;--to&lt;/span&gt; 75   &lt;span class="c"&gt;# → self-contained .clip.html&lt;/span&gt;

&lt;span class="c"&gt;# 5. Or skip the wrapper entirely — passive mode&lt;/span&gt;
agentbox init claude    &lt;span class="c"&gt;# hooks → every claude session, recorded&lt;/span&gt;
agentbox mcp &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; @modelcontextprotocol/server-everything
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No install, no config, no accounts. Sessions land in &lt;code&gt;./.agentbox/sessions/&lt;/code&gt; next to your repo—commit them if you want receipts in git history.&lt;/p&gt;




&lt;h2&gt;
  
  
  Passive Mode: No Wrapper Needed
&lt;/h2&gt;

&lt;p&gt;Wrapping is for flights you know about in advance. &lt;strong&gt;Adapters&lt;/strong&gt; are for the ones you don't.&lt;/p&gt;

&lt;p&gt;For Claude Code, you can merge hooks into your settings with a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentbox init claude         &lt;span class="c"&gt;# merges hooks into .claude/settings.json&lt;/span&gt;
agentbox init claude &lt;span class="nt"&gt;--local&lt;/span&gt; &lt;span class="c"&gt;# .claude/settings.local.json instead&lt;/span&gt;
agentbox init claude &lt;span class="nt"&gt;--remove&lt;/span&gt; &lt;span class="c"&gt;# clean uninstall&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the next session on, Claude Code quietly feeds every event to &lt;code&gt;agentbox hook claude&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Receipt: Real Output from &lt;code&gt;agentbox demo&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Every session ends with a one-page flight receipt. Here's real output from the demo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⬢ M A Y D A Y  R E C E I P T
┌──────────────────────────────────────────────────┐
│ session          demo-deploy                      │
│ command          node examples/fake-agen…         │
│ started          2026-09-24 18:40:52              │
│ duration         5.4s                             │
│ exit code        0 (clean landing)                │
├──────────────────────────────────────────────────┤
│ tool calls       7                                │
│ · bash("git status --s…                           │
│ · write(src/deploy.sh …                           │
│ · bash("./deploy.sh --…                           │
│ … +4 more                                         │
│ shell commands   1                                │
│ · rm -rf /tmp/old-buil…                           │
│ files touched    2 · 1 written · 1 edited         │
│ · src/deploy.sh wrote                             │
│ · config.yaml edited                              │
│ urls hit         1                                │
│ output volume    808 B across 17 lines            │
│ stderr lines     0                                │
│ humans consulted 0 (unsupervised flight)          │
├──────────────────────────────────────────────────┤
│ events recorded  19                               │
│ tamper chain     sha256 · intact                  │
└──────────────────────────────────────────────────┘
uneventful flight. the best kind.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Agentic tooling is moving fast, and trust is the bottleneck. Before you let an agent run &lt;code&gt;terraform apply&lt;/code&gt; or &lt;code&gt;kubectl delete&lt;/code&gt;, you need to know you can &lt;strong&gt;audit exactly what happened&lt;/strong&gt;—and prove it wasn't altered.&lt;/p&gt;

&lt;p&gt;AGENTBOX fills that gap with a beautiful, minimal approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No cloud dependency&lt;/strong&gt; — everything stays on your machine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No SDK integration&lt;/strong&gt; — wrap any command, or use passive hooks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographically verifiable&lt;/strong&gt; — hash-chained events make tampering detectable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human-readable&lt;/strong&gt; — receipts and replay make the data accessible, not just forensic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's the kind of tool that should be in every agent operator's toolkit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Head over to the repo and try the demo:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/arunsoman/agentbox" rel="noopener noreferrer"&gt;github.com/arunsoman/agentbox&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agentbox-cli demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're running agents in production (or just want to know what they're really doing), give it a star and let the author know what you think.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you used AGENTBOX or a similar auditing tool for your agents? Share your experience in the comments below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>javascript</category>
      <category>cli</category>
    </item>
    <item>
      <title>Nirdosha: a language that treats correctness, durability, and identity as compiler problems</title>
      <dc:creator>Arun Soman</dc:creator>
      <pubDate>Sat, 22 Aug 2026 18:03:51 +0000</pubDate>
      <link>https://dev.to/aarun_soman_/nirdosha-a-language-that-treats-correctness-durability-and-identity-as-compiler-problems-3105</link>
      <guid>https://dev.to/aarun_soman_/nirdosha-a-language-that-treats-correctness-durability-and-identity-as-compiler-problems-3105</guid>
      <description>&lt;p&gt;Every language promises safety. Nirdosha's example suite reads like an audit of that promise — each file exists to demonstrate one guarantee, with the rejected programs living in the test suite.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ownership without a garbage-collector personality. box moves on assignment; a second use is a compile-time error. &amp;amp; borrows read without taking. The same affine discipline applies to every resource handle — files, sockets, database connections, message queues, sandboxes — so "forgot to close it" is a type error too.
&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="n"&gt;nir&lt;/span&gt;
&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;box&lt;/span&gt; &lt;span class="nb"&gt;i64&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;i64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;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;let&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;box&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;box&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// moves `a`; using it again would not typecheck&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Effects you don't annotate until you want to. Effects are inferred by default; if you declare effect(pure) and the body does I/O, that's a compile error, not a linter warning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Concurrency that can't alias. spawn's arguments move exactly like a normal call's, so two concurrent computations can never hold the same affine value. Channels are multi-producer, multi-consumer; sending a box moves it across the wire. The same chan/send/recv syntax works across an OS-process boundary via sandbox — the transport changes, the source doesn't.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Transactions as a language construct. transact generates an idempotency key before anything runs, records intent durably, verifies before committing, compensates on failure, and leaves non-terminal entries for replay after a crash. If the retry budget runs out, it traps rather than guessing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Identity in the type system. Nirdosha is an OIDC relying party: it validates JWTs against JWKS and never mints identity tokens. A requires(role: "physician") function's value is unobtainable without proof — acquire is the only path, and calling it directly is a compile-time error.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Math for the mission. Dense linear algebra, a linear Kalman filter, and WGS84 geodesy are builtins. The benchmark suite isn't "solve one huge system" — it's 200,000 predict/update steps of a constant-velocity tracker, because that's the actual workload shape.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Apps, not toys. nirdosha serve runs real applications from the examples folder: an online store (SQLite-backed CRUD, checkout against a mock payment gateway, order events on a message queue), a revenue-assurance tool (reconciliation as a single SQL statement, dashboards derived by naming convention), and an enterprise trade-finance platform with maker-checker approval governance. The web UIs are derived from structs via emit-ui — not hand-written.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The examples are candid about what the language can't do yet — no string concatenation, affine handles without borrowing, fixed entity counts in actor simulations. That honesty is the point: every limitation is documented in the file that demonstrates the feature.&lt;br&gt;
&lt;a href="https://github.com/arunsoman/nirdosha" rel="noopener noreferrer"&gt;try it out&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>programming</category>
      <category>compilers</category>
      <category>languages</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
