<?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: Avinash Gehi</title>
    <description>The latest articles on DEV Community by Avinash Gehi (@avinash_gehi30).</description>
    <link>https://dev.to/avinash_gehi30</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%2F3984246%2F85c1efac-434e-4539-88e0-48dfd7c7a847.png</url>
      <title>DEV Community: Avinash Gehi</title>
      <link>https://dev.to/avinash_gehi30</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/avinash_gehi30"/>
    <language>en</language>
    <item>
      <title>I wrote a dependency scanner with zero dependencies. The parser was easy; the terminal was not.</title>
      <dc:creator>Avinash Gehi</dc:creator>
      <pubDate>Mon, 31 Aug 2026 16:16:59 +0000</pubDate>
      <link>https://dev.to/avinash_gehi30/i-wrote-a-dependency-scanner-with-zero-dependencies-the-parser-was-easy-the-terminal-was-not-2ll7</link>
      <guid>https://dev.to/avinash_gehi30/i-wrote-a-dependency-scanner-with-zero-dependencies-the-parser-was-easy-the-terminal-was-not-2ll7</guid>
      <description>&lt;p&gt;The premise was too neat to pass up: &lt;strong&gt;a tool that finds dependency problems should not have dependencies.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;depx&lt;/code&gt; reads a repository's source, extracts every import, resolves each one against the manifest and the install tree, and reports where the two disagree. Twelve languages. Fully offline. &lt;code&gt;"dependencies": {}&lt;/code&gt; and no &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Built in 72 hours for &lt;a href="https://raptors.dev" rel="noopener noreferrer"&gt;Zero Dependency 2026&lt;/a&gt;, run by Hackathon Raptors, where the single rule is that your shipped manifest ships empty.&lt;/p&gt;

&lt;p&gt;The interesting part was not "can you avoid npm." You can. The interesting part was discovering which of my reflexes were load-bearing and which were just habit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this tool, specifically
&lt;/h2&gt;

&lt;p&gt;A 2025 USENIX Security study fed 576,000 AI-generated code samples through a checker: &lt;strong&gt;19.7% of the packages the models recommended did not exist.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The inventions repeat. Ask ten models for an HTTP retry helper and a good number reach for the same plausible name. So an attacker doesn't guess — they read what the models suggest, register those names first, and wait. You run &lt;code&gt;npm install&lt;/code&gt;, the name resolves, and you're compromised having typed everything correctly. The industry calls it &lt;strong&gt;slopsquatting&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's statically visible. Your code imports a name; your manifest doesn't have it; nothing on disk provides it. That's the whole detection.&lt;/p&gt;




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

&lt;p&gt;Sixteen substitutions. Three worth talking about.&lt;/p&gt;

&lt;h3&gt;
  
  
  The JavaScript parser → a masking lexer
&lt;/h3&gt;

&lt;p&gt;Every tool in this category — &lt;code&gt;depcheck&lt;/code&gt;, &lt;code&gt;dependency-cruiser&lt;/code&gt;, &lt;code&gt;madge&lt;/code&gt;, every bundler — installs a full JavaScript parser to answer one question: &lt;em&gt;which strings are import specifiers?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You don't need a syntax tree for that. You need to know which string literals sit in import position. So I &lt;strong&gt;mask&lt;/strong&gt; the source: blank out comments, string bodies, template literals and regex literals while preserving byte offsets, then test what precedes each surviving string literal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// after masking, every remaining string literal is real code&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;masked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trimEnd&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;from$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b&lt;/span&gt;&lt;span class="sr"&gt;import$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;About 150 lines against a 100 KB dependency. It can't be fooled by &lt;code&gt;// import 'fake'&lt;/code&gt; or &lt;code&gt;const s = "require('fake')"&lt;/code&gt;, both of which are in the test suite.&lt;/p&gt;

&lt;p&gt;Preserving offsets is the trick that makes it work. The masked text is the same length as the original, so an offset into one is an offset into the other, and you can report &lt;code&gt;src/app.js:5:34&lt;/code&gt; from a lexer that never built a node.&lt;/p&gt;

&lt;h3&gt;
  
  
  The terminal UI → &lt;code&gt;node:readline&lt;/code&gt; and a dozen escape codes
&lt;/h3&gt;

&lt;p&gt;This is the one that surprised me.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;depx&lt;/code&gt; has a full-screen interface — alternate screen, arrow keys, live search, &lt;code&gt;$EDITOR&lt;/code&gt; on Enter. The reflex here is &lt;a href="https://github.com/vadimdemedes/ink" rel="noopener noreferrer"&gt;&lt;code&gt;ink&lt;/code&gt;&lt;/a&gt;: React, plus a reconciler, plus the Yoga layout engine compiled to WebAssembly. To draw a list you can arrow through in a terminal.&lt;/p&gt;

&lt;p&gt;Every piece is already in Node:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;What you'd install&lt;/th&gt;
&lt;th&gt;What's already there&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Decode arrow keys&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;keypress&lt;/code&gt;, &lt;code&gt;ink&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;readline.emitKeypressEvents()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read keys unbuffered&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;blessed&lt;/code&gt;, &lt;code&gt;enquirer&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;stdin.setRawMode(true)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alternate screen, hide cursor&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;blessed&lt;/code&gt;, &lt;code&gt;cli-cursor&lt;/code&gt;, &lt;code&gt;ora&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;\x1b[?1049h&lt;/code&gt;, &lt;code&gt;\x1b[?25l&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terminal size + resize&lt;/td&gt;
&lt;td&gt;&lt;code&gt;term-size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;stdout.columns&lt;/code&gt;, the &lt;code&gt;resize&lt;/code&gt; event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Two-pane layout, text wrap&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ink&lt;/code&gt; + Yoga (WASM)&lt;/td&gt;
&lt;td&gt;~30 lines of arithmetic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Highlight a row&lt;/td&gt;
&lt;td&gt;&lt;code&gt;chalk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;util.styleText('inverse')&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;If you take one thing from this post, take the design decision underneath it:&lt;/strong&gt; the state machine and the frame renderer are &lt;em&gt;pure functions of &lt;code&gt;(state, size)&lt;/code&gt;&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;// → { state, action }&lt;/span&gt;
&lt;span class="nf"&gt;renderFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;// → exactly `rows` strings of exactly `cols` width&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only &lt;code&gt;runTui()&lt;/code&gt; touches stdin, stdout or the process. Which means &lt;strong&gt;47 tests drive the entire interface without a terminal&lt;/strong&gt;, asserting on frames as plain strings. Testing an &lt;code&gt;ink&lt;/code&gt; app normally means installing &lt;code&gt;ink-testing-library&lt;/code&gt; on top of &lt;code&gt;ink&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That invariant — &lt;em&gt;every frame is exactly &lt;code&gt;rows&lt;/code&gt; lines of exactly &lt;code&gt;cols&lt;/code&gt; display width&lt;/em&gt; — turned out to be the highest-leverage assertion in the project. One property, three bugs I would never have caught by eye:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a footer emitted without padding, invisible because the draw loop's &lt;code&gt;\x1b[K&lt;/code&gt; was cleaning up after it&lt;/li&gt;
&lt;li&gt;a chrome constant that assumed the detail panel always existed, leaving a stray row on every empty frame&lt;/li&gt;
&lt;li&gt;three rows in the empty state emitted as &lt;code&gt;''&lt;/code&gt; instead of a full-width run — two columns wide instead of seventy-eight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of them looked wrong on screen. All of them are obvious to &lt;code&gt;displayWidth(line) === cols&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;chalk&lt;/code&gt;, &lt;code&gt;minimist&lt;/code&gt;, &lt;code&gt;string-width&lt;/code&gt;, &lt;code&gt;cli-table3&lt;/code&gt;, &lt;code&gt;globby&lt;/code&gt;, &lt;code&gt;jest&lt;/code&gt;, &lt;code&gt;esbuild&lt;/code&gt;…
&lt;/h3&gt;

&lt;p&gt;Mostly one-liners now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;styleText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parseArgs&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:util&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// chalk, minimist&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                    &lt;span class="c1"&gt;// jest&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:assert/strict&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;&lt;code&gt;util.styleText&lt;/code&gt; even honours &lt;code&gt;NO_COLOR&lt;/code&gt; and does TTY detection for you, which is most of why people install &lt;code&gt;chalk&lt;/code&gt; in the first place.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the standard library made painful
&lt;/h2&gt;

&lt;p&gt;Three things, in ascending order of annoyance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Nothing tells you how wide a string is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;'日本語'.length&lt;/code&gt; is 3. It occupies 6 terminal columns. That gap is the entire reason &lt;code&gt;string-width&lt;/code&gt; exists, and if you're aligning columns you cannot ignore it. So: strip ANSI, iterate code points, skip control characters and combining marks, and count East Asian Wide and emoji ranges as two.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mh"&gt;0x20&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x7f&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mh"&gt;0xa0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// control&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x0300&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mh"&gt;0x036f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// combining mark&lt;/span&gt;
&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;isWide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;About 40 lines. Not hard, just genuinely absent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Regex or division?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A lexer that masks regex literals has to decide what &lt;code&gt;/&lt;/code&gt; means. &lt;code&gt;a / b&lt;/code&gt; is division; &lt;code&gt;a = /b/&lt;/code&gt; is a literal. There is no stdlib help; you use the same preceding-token heuristic real lexers use — a set of characters and keywords after which a &lt;code&gt;/&lt;/code&gt; can only start a regex.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REGEX_PRECEDERS&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;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REGEX_KEYWORDS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(&lt;/span&gt;&lt;span class="sr"&gt;return|typeof|instanceof|in|of|new|delete|void|yield|await&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;*$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not infallible. Documented as such in the README, because a limitation you name is a limitation and one you hide is a bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Node ships no TOML support. At any version.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was the only true gap — the one place the constraint forced original work rather than a smaller reimplementation. &lt;code&gt;Cargo.toml&lt;/code&gt; and &lt;code&gt;pyproject.toml&lt;/code&gt; are not optional if you claim Rust and Python support.&lt;/p&gt;

&lt;p&gt;So: a subset reader for what a dependency manifest can actually contain — tables, dotted keys, strings, numbers, booleans, inline tables, inline arrays. Arrays-of-tables and datetimes are deliberately not implemented.&lt;/p&gt;

&lt;p&gt;The part I'd flag to anyone hand-rolling one: &lt;strong&gt;your comment stripper and your key/value split both have to be quote-aware.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"a&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;b&lt;/span&gt; &lt;span class="c"&gt;# c"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A naive &lt;code&gt;split('=')&lt;/code&gt; and a naive &lt;code&gt;split('#')&lt;/code&gt; each corrupt that line, and most quick TOML readers I've read do exactly one of the two.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Bun 1.4 ships &lt;code&gt;Bun.TOML&lt;/code&gt;. I was targeting Node on a machine with Bun 1.3.14, so it wasn't an option — but a Bun submission could legitimately delete this whole file.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The afternoon that vanished
&lt;/h2&gt;

&lt;p&gt;The interface worked. Arrow keys moved, search filtered, &lt;code&gt;q&lt;/code&gt; restored my prompt.&lt;/p&gt;

&lt;p&gt;And then the process just… sat there. Prompt back, terminal clean, shell unresponsive. &lt;code&gt;ctrl-c&lt;/code&gt; to get out, every time.&lt;/p&gt;

&lt;p&gt;The quit path looked correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;off&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keypress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRawMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ALT_OFF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The listener is gone. Raw mode is off. The stream is paused. The alternate screen is closed. What is holding the event loop open?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;pause()&lt;/code&gt; is not the same as releasing the handle.&lt;/strong&gt; &lt;code&gt;readline.emitKeypressEvents()&lt;/code&gt; leaves a reader attached to the TTY, and a referenced TTY handle keeps libuv's loop alive whether or not you're reading from it. The fix is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unref&lt;/span&gt;&lt;span class="p"&gt;?.();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What cost me the afternoon wasn't the fix — it was that &lt;strong&gt;every symptom pointed away from the cause.&lt;/strong&gt; The terminal was restored, so the teardown "worked." The prompt was back, so the program "exited." I went looking for a stray &lt;code&gt;setInterval&lt;/code&gt; and an unresolved promise before I thought to question &lt;code&gt;pause()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The lesson I'd generalise: when a Node process won't exit, stop reading your cleanup code and start asking which handles are still &lt;em&gt;referenced&lt;/em&gt;. Those are different questions, and only the second one matters.&lt;/p&gt;

&lt;p&gt;It's now a regression test that injects fake streams and asserts &lt;code&gt;unref&lt;/code&gt; was called — no terminal required, because of the pure-function split above.&lt;/p&gt;







&lt;h2&gt;
  
  
  Two things about terminal UIs that have nothing to do with dependencies
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Your success state is a UI state.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Running &lt;code&gt;depx&lt;/code&gt; on a healthy repository opened onto a header, a footer, and twenty blank rows. It was behaving correctly — there are no findings, so there is nothing to browse — but a mostly-empty screen reads as a program that failed to load. It was happening on exactly the repositories the tool should be &lt;em&gt;reassuring&lt;/em&gt; about.&lt;/p&gt;

&lt;p&gt;The fix wasn't code, it was noticing. Now the no-findings case gets a composed panel: a mark, a headline, what was actually scanned, and the nested projects that were skipped along with why. And the three ways of having nothing to show stay distinct, because a repository whose findings are all suppressed by config is &lt;strong&gt;configured, not clean&lt;/strong&gt;, and one with no source files at all was never really analysed.&lt;/p&gt;

&lt;p&gt;If your empty state is a blank screen, you didn't design an empty state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I nearly shipped a lie, and it would have demoed better.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The analysis is fast: four files in 40ms, six thousand in 0.70s. So when I added a scanning screen — real file counts, streamed from the walker through an &lt;code&gt;onProgress&lt;/code&gt; callback — it flickered past faster than you can perceive it.&lt;/p&gt;

&lt;p&gt;The obvious fix was a minimum display time. Five hundred milliseconds and the demo looks &lt;em&gt;great&lt;/em&gt;. Everyone does this.&lt;/p&gt;

&lt;p&gt;I didn't, and I think the reason generalises. A floor on your loading indicator means shipping a slower tool so that it looks busier — trading a real strength (it's fast) for a manufactured one (it looks like it's working hard). The screen is now up for exactly as long as the walk takes: a flicker on a small project, about 0.4 seconds on four thousand files, and genuinely useful on a monorepo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If your progress indicator needs a minimum duration to be visible, you don't need a progress indicator. You need to print the number.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The bug report that was really a design review
&lt;/h2&gt;

&lt;p&gt;Someone watching me demo it asked the question that reframed the whole thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Why do I have to quit the UI, run another command, and open it again? Why the hell am I bouncing between the two?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They were right, and I'd been blind to it because I built the pieces in the order the code wanted rather than the order a person uses them. The interface was a view onto &lt;strong&gt;one&lt;/strong&gt; subcommand — &lt;code&gt;check&lt;/code&gt;. The zero-dependency rule check and the vendoring scan were separate commands, so answering three questions about the same repository meant three round trips through a shell.&lt;/p&gt;

&lt;p&gt;That's not a missing feature. That's having modelled the tool as a set of commands with a UI bolted onto one of them, instead of as a thing you point at a repository.&lt;/p&gt;

&lt;p&gt;Three keys now — &lt;code&gt;f&lt;/code&gt; findings, &lt;code&gt;z&lt;/code&gt; rule, &lt;code&gt;v&lt;/code&gt; copied source — switch view from anywhere. Both analyses run behind the single scan screen, so switching is instant. It turned out to be two passes and not three, because &lt;code&gt;verifyZeroDep()&lt;/code&gt; already returned the vendoring scan; the data had been sitting there the whole time, reachable only by exiting and typing a different word.&lt;/p&gt;

&lt;p&gt;The change also caught a bug it had just introduced. The fuller footer overflowed 76 columns, and my all-or-nothing fallback silently swapped in a shorter hint line — one that didn't mention the new views at all. So the feature existed and was undiscoverable at exactly the widths most people use. The footer is tiered now, and drops the movement hints &lt;em&gt;before&lt;/em&gt; the view list:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A key you cannot discover is a feature that does not exist.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The rule I wish I'd started with
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;depx&lt;/code&gt; opened the interface when you ran it bare, and printed a report when you gave it a path. Two behaviours, and the boundary was "did you pass an argument" — which is a fact about your typing, not about your intent.&lt;/p&gt;

&lt;p&gt;It's one rule now:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;No subcommand, in a terminal → the interface. Everything else → the report.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Naming a subcommand is the signal that you want &lt;em&gt;that command's output&lt;/em&gt;. So &lt;code&gt;depx check .&lt;/code&gt; is text and always was. So is a pipe, a redirect, a CI job, &lt;code&gt;--json&lt;/code&gt;, &lt;code&gt;--quiet&lt;/code&gt;. And &lt;code&gt;depx&lt;/code&gt; and &lt;code&gt;depx ./some/project&lt;/code&gt; both mean "show me this project", where &lt;em&gt;how&lt;/em&gt; it's shown depends on whether a person is looking.&lt;/p&gt;

&lt;p&gt;The property that makes a rule this broad safe: &lt;strong&gt;every scripted invocation either names a subcommand or isn't attached to a terminal.&lt;/strong&gt; So no script's behaviour can change, which is the actual thing you're protecting. Two tests pin it — a bare path through a pipe is still the report and still exits 1, and a named subcommand is never the interface.&lt;/p&gt;

&lt;p&gt;I'd been treating "adapt to the terminal" as the risky thing. It isn't. The risky thing is adapting on a signal a script can accidentally produce.&lt;/p&gt;

&lt;h2&gt;
  
  
  The insight I actually care about
&lt;/h2&gt;

&lt;p&gt;Replacing packages was the fun part. The hard part was deciding &lt;strong&gt;what the tool is allowed to claim.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;depx&lt;/code&gt; is offline. It can prove an import resolves to nothing &lt;em&gt;in the project in front of it&lt;/em&gt;. It cannot prove a package doesn't exist anywhere — that needs a copy of the registry index, which is precisely the kind of dependency this tool exists without.&lt;/p&gt;

&lt;p&gt;So there are two findings where a lazier tool would have one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ghost&lt;/code&gt;&lt;/strong&gt; — imported, and nothing here provides it. High confidence, because the full resolution universe was visible: either the language's manifest is authoritative (Go won't compile an import absent from &lt;code&gt;go.mod&lt;/code&gt;) or an install tree was on disk to check against.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;undeclared&lt;/code&gt;&lt;/strong&gt; — imported, not declared, and &lt;em&gt;no evidence available to judge further&lt;/em&gt;. Deliberately weak.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That distinction exists because &lt;strong&gt;a false ghost is the most damaging error this tool can make.&lt;/strong&gt; It sends someone hunting a supply-chain compromise that isn't there. So the design leans away from it everywhere: modules the repo defines, a package importing itself, subpath imports, bundler aliases, &lt;code&gt;#internal/x&lt;/code&gt;, &lt;code&gt;$lib/w&lt;/code&gt;, Python's &lt;code&gt;__future__&lt;/code&gt;, Ruby's &lt;code&gt;English&lt;/code&gt; — all excluded before judgement.&lt;/p&gt;

&lt;p&gt;An empty &lt;code&gt;.venv&lt;/code&gt; directory does not promote anything to a ghost either. &lt;strong&gt;A directory is not evidence.&lt;/strong&gt; It has to contain something.&lt;/p&gt;

&lt;p&gt;I validated against eighteen real repositories — every public submission I could find, plus working Go, Rust, Python and TypeScript projects with trees installed. That produced &lt;strong&gt;twenty-one defects, mostly false positives&lt;/strong&gt;, each now a regression test named for the case that caused it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Rust: a bin target importing its own crate is not a ghost&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Go: an // indirect requirement is not reported as dead&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Python: a URL requirement line is not a package named "git"&lt;/code&gt;&lt;br&gt;
&lt;code&gt;Java: a Maven coordinate is never reported as dead&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That last one is a whole tier of the design. In Java, &lt;code&gt;import org.apache.commons.lang3.StringUtils&lt;/code&gt; is satisfied by the artifact &lt;code&gt;org.apache.commons:commons-lang3&lt;/code&gt;. The namespace and the coordinate are unrelated strings, connected only by a mapping that lives on Maven Central. Resolving that offline means shipping the registry index.&lt;/p&gt;

&lt;p&gt;So Java, C#, PHP and C/C++ are &lt;strong&gt;tier 3: ghost detection off by design.&lt;/strong&gt; They report file inventory and declared dependencies and stay silent on ghosts and dead code.&lt;/p&gt;

&lt;p&gt;Shipping a feature that says "I can't answer this" is less satisfying than shipping one that guesses. It's also the only honest option, and I think it's the thing in this project I'd defend hardest.&lt;/p&gt;




&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;237 tests, 55 suites&lt;/strong&gt;, &lt;code&gt;node:test&lt;/code&gt;, no config file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;16 stdlib substitutions&lt;/strong&gt;, each documented with reasoning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;12 languages&lt;/strong&gt; across 9 adapters&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;6,000 files scanned in 0.70s&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Scan screen driven by &lt;strong&gt;real file counts, with no minimum display time&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Single-file build that's &lt;strong&gt;byte-identical across runs&lt;/strong&gt; — and the verify step diffs the bundle's &lt;em&gt;behaviour&lt;/em&gt; against the source tree, so a build that changed behaviour fails even if it hashed the same&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dependencies: {}&lt;/code&gt;, no &lt;code&gt;node_modules&lt;/code&gt;, and the tool verifies that claim about itself&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Would I do it again
&lt;/h2&gt;

&lt;p&gt;For a scanner? Yes, and I'd keep it. The dependency count of a tool that audits dependencies is not a gimmick, it's the argument.&lt;/p&gt;

&lt;p&gt;For a product with a deadline? No. I'd install &lt;code&gt;ink&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But I know what's inside &lt;code&gt;ink&lt;/code&gt; now, and that's worth something. The uncomfortable finding of the weekend is how few of my reflexes survived contact with the standard library. &lt;code&gt;chalk&lt;/code&gt; became one import. &lt;code&gt;minimist&lt;/code&gt; became one import. &lt;code&gt;jest&lt;/code&gt; became one import. The genuinely irreplaceable thing turned out to be TOML — a text format from 2013.&lt;/p&gt;

&lt;p&gt;We didn't outsource the hard parts. We outsourced the parts we stopped looking at.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built for &lt;a href="https://raptors.dev" rel="noopener noreferrer"&gt;Zero Dependency 2026&lt;/a&gt; by Hackathon Raptors. Source: &lt;a href="https://github.com/Avi36005/ZeroDependency_Team_Kryptonite" rel="noopener noreferrer"&gt;github.com/Avi36005/ZeroDependency_Team_Kryptonite&lt;/a&gt; — MIT.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>I ported croniter to Rust and got 228/228. That number proved nothing</title>
      <dc:creator>Avinash Gehi</dc:creator>
      <pubDate>Fri, 07 Aug 2026 13:08:34 +0000</pubDate>
      <link>https://dev.to/avinash_gehi30/i-ported-croniter-to-rust-and-got-228228-that-number-proved-nothing-3lgd</link>
      <guid>https://dev.to/avinash_gehi30/i-ported-croniter-to-rust-and-got-228228-that-number-proved-nothing-3lgd</guid>
      <description>&lt;p&gt;A port that compiles and passes its tests isn't evidence the port is correct. It's evidence that whoever wrote the port also controlled the tests. Generating a port is nearly free now. Proving it holds up is the part almost nobody does.&lt;/p&gt;

&lt;p&gt;So here's what I actually did to try to falsify my own &lt;a href="https://github.com/pallets-eco/croniter" rel="noopener noreferrer"&gt;croniter&lt;/a&gt; → Rust port, and where it fell short.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Run the &lt;em&gt;original&lt;/em&gt; tests, hash-pinned
&lt;/h2&gt;

&lt;p&gt;Not a translated suite. The actual upstream files, SHA-256 fingerprinted before a line of Rust existed, wired to Rust through a PyO3 bridge. &lt;code&gt;git log -- tests/original/&lt;/code&gt; shows one commit: the vendoring.&lt;/p&gt;

&lt;p&gt;Then the step I'd argue is mandatory: &lt;strong&gt;build the bridge against a deliberately wrong stub first and confirm the tests fail.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;222 failed, 6 passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's success — it proves the suite is importing and judging Rust before any correct logic exists to muddy the signal. And note the 6: my stub returned &lt;code&gt;False&lt;/code&gt; from &lt;code&gt;is_valid&lt;/code&gt;, which satisfies every test asserting an expression is invalid. Even my broken baseline had false positives.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Sabotage your own green suite
&lt;/h2&gt;

&lt;p&gt;228/228 has two explanations and you can't tell them apart from the green: the port is right, or the suite can't fail. So I broke it on purpose — two single-token changes in two unrelated files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;consts.rs   hour range (0,23) -&amp;gt; (0,22)  -&amp;gt;  32 failed, 196 passed
expand.rs   wrap length +1 -&amp;gt; +2          -&amp;gt;   2 failed, 226 passed
reverted                                  -&amp;gt; 228 passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ten minutes, and it's the difference between a measurement and a decoration. I did the same to the benchmark checksum.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Make the library contradict itself
&lt;/h2&gt;

&lt;p&gt;The technique I'd steal from this project. croniter exposes three APIs answering overlapping questions — &lt;code&gt;get_next&lt;/code&gt;, &lt;code&gt;get_prev&lt;/code&gt;, &lt;code&gt;match&lt;/code&gt; — and they &lt;em&gt;must&lt;/em&gt; agree. If &lt;code&gt;get_next(start)&lt;/code&gt; returns &lt;code&gt;N&lt;/code&gt;, nothing strictly between may &lt;code&gt;match&lt;/code&gt;, and &lt;code&gt;N&lt;/code&gt; must &lt;code&gt;match&lt;/code&gt;. A violation means the library contradicts itself and one answer is wrong under any reading of cron semantics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's an oracle with no external reference.&lt;/strong&gt; No second implementation, no spec, no human. The library grades itself.&lt;/p&gt;

&lt;p&gt;My first one was worthless. It checked one property, on naive datetimes only, never called &lt;code&gt;get_prev&lt;/code&gt;. 19,440 cases, zero findings — and I briefly read zero as correctness. It was evidence the question was too easy. &lt;strong&gt;An invariant that can't fail isn't an oracle.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The rewrite checked five properties, called &lt;code&gt;get_prev&lt;/code&gt;, and biased half its start times to within four hours of a real DST transition — including Australia/Lord_Howe, the only zone on Earth with a 30-minute shift. It found two real bugs in croniter, both now filed upstream (&lt;a href="https://github.com/pallets-eco/croniter/issues/258" rel="noopener noreferrer"&gt;#258&lt;/a&gt;, &lt;a href="https://github.com/pallets-eco/croniter/issues/259" rel="noopener noreferrer"&gt;#259&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tz&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zoneinfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ZoneInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Australia/Lord_Howe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&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="mi"&gt;43&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;croniter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0 * * * *&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get_next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 03:00+11:00
&lt;/span&gt;&lt;span class="nf"&gt;croniter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0 * * * *&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nxt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get_prev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# 02:30+11:00  &amp;lt;- AFTER start
&lt;/span&gt;&lt;span class="n"&gt;croniter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0 * * * *&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&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;# True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;match&lt;/code&gt; returns &lt;code&gt;True&lt;/code&gt; for a &lt;strong&gt;minute-0&lt;/strong&gt; schedule at &lt;strong&gt;minute 30&lt;/strong&gt;. In a normal 1-hour zone the same code path lands on 03:00, which &lt;em&gt;is&lt;/em&gt; valid — so the bug is invisible everywhere except the 30-minute shift. Which is exactly why the generator was pointed there.&lt;/p&gt;

&lt;p&gt;The second: &lt;code&gt;croniter_range&lt;/code&gt;'s stop test is &lt;code&gt;v &amp;lt; stop&lt;/code&gt;, and CPython ignores &lt;code&gt;tzinfo&lt;/code&gt; when both operands share it. Across a DST transition it compares wall-clock instead of elapsed time — returning 1 result where 6 exist, or results outside the interval you asked for. Silent, no exception.&lt;/p&gt;

&lt;p&gt;My port reproduces both deliberately. A port's job is to behave like the thing it ports, including where that's wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The bug 228 passing tests could not find
&lt;/h2&gt;

&lt;p&gt;Differential fuzzing: same probe under two interpreters, comparing values &lt;em&gt;and exception types&lt;/em&gt;. Adding timezone-aware inputs surfaced &lt;strong&gt;221 divergences in 164,500&lt;/strong&gt; — all one cause, and it was a type, not a value. croniter raises a bare &lt;code&gt;ValueError&lt;/code&gt;; my port raised &lt;code&gt;CroniterError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CroniterError&lt;/code&gt; subclasses &lt;code&gt;ValueError&lt;/code&gt;. Every &lt;code&gt;except ValueError&lt;/code&gt; caught it. &lt;strong&gt;The suite was green at 228/228 before and after.&lt;/strong&gt; It could not have found this, no matter how long I ran it.&lt;/p&gt;

&lt;p&gt;That's the whole argument for differential fuzzing in one paragraph. Zero value divergences, though — the date math was right, only a label was wrong. Final run: 160,500 inputs, 0 divergences.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. What I'd take back
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The first oracle cost a day and taught nothing.&lt;/strong&gt; I should have asked "what input would falsify this?" &lt;em&gt;before&lt;/em&gt; running it for an hour.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Triage cost more than the hunt.&lt;/strong&gt; One harness gave 927 raw findings; 750 were documented behaviour. An earlier one gave 1,408 findings that were entirely my own bug in the checker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The fuzzer tests the bridge, not the shipped binary.&lt;/strong&gt; Both sides run under Python, so &lt;code&gt;core&lt;/code&gt; is validated &lt;em&gt;as called through PyO3&lt;/em&gt;. The artifact judges receive is one layer removed from the evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;228/228 hid a hole in the deliverable.&lt;/strong&gt; Every timezone test supplies a &lt;code&gt;tzinfo&lt;/code&gt;, so they all went through the bridge — while the standalone binary couldn't do DST at all. A suite measures the path the tests take. Mine bypassed a third of the product and reported full marks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I wrote an unverified claim into my own README&lt;/strong&gt; (a Docker build that had never run). Caught it late, marked it unverified rather than deleting it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The number nobody prints:&lt;/strong&gt; the suite runs in 1.54s against Python and ~1.8s against my 25x-faster Rust. Every call crosses FFI. Both facts are true at once.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;p&gt;228/228 on unmodified tests · 160,500 fuzz inputs, 0 divergences · 2 upstream bugs filed · 0 &lt;code&gt;unsafe&lt;/code&gt; (compiler-enforced) · 25.3x mean, 26.1x p99, 3.2x smaller RSS · 0 test files modified.&lt;/p&gt;

&lt;p&gt;Every figure was observed on one machine and written down after the run. The one claim I couldn't verify is marked as unverified in the repo rather than dropped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Avi36005/Portmortem-Team-Kryptonite" rel="noopener noreferrer"&gt;github.com/Avi36005/Portmortem-Team-Kryptonite&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>python</category>
    </item>
    <item>
      <title>Why stateless LLMs can't make consistent decisions, and how Hindsight fixed that</title>
      <dc:creator>Avinash Gehi</dc:creator>
      <pubDate>Mon, 15 Jun 2026 12:17:45 +0000</pubDate>
      <link>https://dev.to/avinash_gehi30/why-stateless-llms-cant-make-consistent-decisions-and-how-hindsight-fixed-that-8nn</link>
      <guid>https://dev.to/avinash_gehi30/why-stateless-llms-cant-make-consistent-decisions-and-how-hindsight-fixed-that-8nn</guid>
      <description>&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.amazonaws.com%2Fuploads%2Farticles%2Fd3ioz0c4i1ye2ihp3jot.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.amazonaws.com%2Fuploads%2Farticles%2Fd3ioz0c4i1ye2ihp3jot.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fc0a3b82pjd528kka1wo5.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.amazonaws.com%2Fuploads%2Farticles%2Fc0a3b82pjd528kka1wo5.png" alt=" " width="800" height="439"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fs41f1u5ynjiao1imtwby.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.amazonaws.com%2Fuploads%2Farticles%2Fs41f1u5ynjiao1imtwby.png" alt=" " width="800" height="396"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F1rksh2b38pfl67x6j5zb.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.amazonaws.com%2Fuploads%2Farticles%2F1rksh2b38pfl67x6j5zb.png" alt=" " width="800" height="345"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F97qo58m33956r7t0ep2p.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2F97qo58m33956r7t0ep2p.jpeg" alt=" " width="800" height="520"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2F5vreincbvwppzclwp8ta.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.amazonaws.com%2Fuploads%2Farticles%2F5vreincbvwppzclwp8ta.png" alt=" " width="800" height="347"&gt;&lt;/a&gt;&lt;br&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.amazonaws.com%2Fuploads%2Farticles%2Fr1t39tzxhd5b5j7sr2fd.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.amazonaws.com%2Fuploads%2Farticles%2Fr1t39tzxhd5b5j7sr2fd.png" alt=" " width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/qV4Go-1dJIA"&gt;
  &lt;/iframe&gt;
I spend most of my time in the frontend. I build dashboards, wire up routes, push the thing to a CDN, and obsess over the half-second between a click and something useful appearing on screen. So when we set out to build &lt;a href="https://github.com/Avi36005/ExceptionOS" rel="noopener noreferrer"&gt;ExceptionOS&lt;/a&gt; — a platform that helps companies make consistent, explainable decisions about business exceptions like refunds, discount approvals, and SLA compensation — the part I owned was the surface: the React app, the deploy pipeline, and a chat-plus-voice assistant that anyone could talk to.&lt;/p&gt;

&lt;p&gt;The interesting problem turned out not to be the UI at all. It was what sat behind it: a memory layer that remembers every decision an organization has ever made, and an assistant that answers questions by recalling from it. This is the story of building that assistant, the dumb mistake I made that made it feel slow and weird, and how a memory system called &lt;a href="https://hindsight.vectorize.io/" rel="noopener noreferrer"&gt;Hindsight&lt;/a&gt; ended up shaping the whole product.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the system actually does
&lt;/h2&gt;

&lt;p&gt;ExceptionOS captures a business exception — say, a customer asking for a refund outside policy — and runs it through a debate. Ten specialized agents look at the case from different angles: one finds the applicable policy, one estimates the financial hit, one assesses churn risk, one digs up similar past cases, and one plays critic and pokes holes in the emerging recommendation. The output is a structured recommendation with reasoning a human can read and override.&lt;/p&gt;

&lt;p&gt;That debate is only as good as its memory. An agent that finds "similar past cases" needs somewhere those cases live. We use Hindsight Cloud for that — &lt;a href="https://vectorize.io/what-is-agent-memory" rel="noopener noreferrer"&gt;agent memory&lt;/a&gt; as a managed service, with three operations we lean on constantly: retain (store a decision), recall (find relevant ones), and reflect (surface patterns over time). Every organization gets its own memory bank, created on first use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;bank&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hindsight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_bank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exceptionos-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;org_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;organization_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Memory bank for organisation &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;org_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; on ExceptionOS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One bank per org means recall is naturally scoped — Acme's assistant never sees Globex's decisions. That property mattered a lot once I started building the front-facing assistant.&lt;/p&gt;

&lt;h2&gt;
  
  
  The assistant: a chat orb that knows your history
&lt;/h2&gt;

&lt;p&gt;The feature I'm proudest of is a floating orb that lives in the corner of every screen. You can type at it or talk to it. Ask "what's our approval rate for contractor exceptions?" and it answers from your organization's actual decision history, then reads the answer aloud.&lt;/p&gt;

&lt;p&gt;The frontend side is deliberately thin. The browser doesn't talk to the memory layer or the LLM directly — it posts a message, an optional bank ID, and the current page context to one backend endpoint, and gets back an answer plus the sources it used:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;askAssistant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;bankId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;context&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&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="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AssistantReply&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/assistant/chat`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;bank_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bankId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&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;Passing &lt;code&gt;context&lt;/code&gt; — the case or page the user is currently looking at — is what makes the assistant feel like it's &lt;em&gt;there with you&lt;/em&gt;. Ask "is this one risky?" while staring at a specific case and it knows what "this" means. Passing &lt;code&gt;bankId&lt;/code&gt; is what makes it org-aware. Two small fields, most of the perceived intelligence.&lt;/p&gt;

&lt;p&gt;On the backend, the endpoint recalls grounding memories from Hindsight and feeds them to the LLM as context. The recall itself is one HTTP call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recall_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bank_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hindsight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bank_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a system prompt tells the model to use those memories only when the question actually calls for them, answer in one to three sentences, and keep a natural spoken tone — because that same text gets sent to &lt;a href="https://github.com/Avi36005/ExceptionOS" rel="noopener noreferrer"&gt;ElevenLabs&lt;/a&gt; for voice synthesis and read back to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake: recalling on "hi"
&lt;/h2&gt;

&lt;p&gt;Here's where I got it wrong. My first version was clean and uniform: every message went through the same path. User says something, we recall from memory, we hand the memories to the model, we answer. Symmetry felt right.&lt;/p&gt;

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

&lt;p&gt;You'd open the orb, type "hi", and wait. Behind that one word the system was doing a full vector recall against the org's entire decision history, pulling five "relevant" memories about refunds and NDAs, and stuffing them into the prompt. The model, dutifully handed a pile of past cases, would respond to "hi" by &lt;em&gt;listing refund precedents&lt;/em&gt;. It was slow — a network round-trip to the memory layer before any greeting — and it was unsettling, like saying hello to someone who immediately recites your file.&lt;/p&gt;

&lt;p&gt;The fix was to admit that not every message is a query. Small talk shouldn't touch memory at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; .!?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;is_smalltalk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalized&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;GREETINGS&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="n"&gt;memories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;is_smalltalk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;memories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nc"&gt;RecallService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get_hindsight_client&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;recall_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bank_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; "hi" → recall five memories → 2-second pause → an awkward dump of past refund cases.&lt;br&gt;
&lt;strong&gt;After:&lt;/strong&gt; "hi" → no recall → instant, warm one-liner inviting you to ask about a case.&lt;/p&gt;

&lt;p&gt;The system prompt reinforces it: greetings get a warm sentence and never enumerate cases; memories get referenced only when the user asks about a case, refund, discount, policy, or decision. The lesson generalizes well beyond greetings. Recall is not free — it costs a round-trip and it costs prompt space — and a memory system is most impressive when it stays quiet until it has something worth saying.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gotcha that cost me an afternoon: metadata is strings only
&lt;/h2&gt;

&lt;p&gt;A quieter lesson lived at the boundary between our data model and Hindsight's. Hindsight's memory metadata accepts string values only, so anything structured — case IDs, financial figures, nested objects — has to be coerced or JSON-encoded before a retain call, or it silently fails to stick. The fix was a small normalizer that every retain passes through. The takeaway: when you adopt a managed memory layer, learn its type contract early — the constraints are usually there for good reasons, and guessing from the client side just wastes an afternoon.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell someone starting this
&lt;/h2&gt;

&lt;p&gt;Shipping the frontend taught me that a memory layer doesn't live in the backend — it leaks into every product decision you make. Whether to recall, when to recall, how much to show, what to read aloud: those are UX calls as much as infrastructure calls. Two small request fields (&lt;code&gt;bank_id&lt;/code&gt;, &lt;code&gt;context&lt;/code&gt;) carried most of the assistant's apparent intelligence. One conditional (&lt;code&gt;is_smalltalk&lt;/code&gt;) carried most of its perceived speed and warmth.&lt;/p&gt;

&lt;p&gt;If you're building something similar, start with the operations — retain, recall, reflect — and resist the urge to apply them uniformly. The product feels smart not when it remembers everything, but when it knows the difference between a question and a hello.&lt;/p&gt;

&lt;p&gt;You can see the project at &lt;a href="https://github.com/Avi36005/ExceptionOS" rel="noopener noreferrer"&gt;github.com/Avi36005/ExceptionOS&lt;/a&gt;, read more about the memory layer in the &lt;a href="https://hindsight.vectorize.io/" rel="noopener noreferrer"&gt;Hindsight docs&lt;/a&gt; and its &lt;a href="https://github.com/vectorize-io/hindsight" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;, or dig into the concept of &lt;a href="https://vectorize.io/what-is-agent-memory" rel="noopener noreferrer"&gt;agent memory&lt;/a&gt; itself.&lt;/p&gt;

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