<?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: Irfan Wani</title>
    <description>The latest articles on DEV Community by Irfan Wani (@irfan_wani).</description>
    <link>https://dev.to/irfan_wani</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%2F1788786%2F1b75ec39-af48-4a58-af19-a894d98ef8e3.jpeg</url>
      <title>DEV Community: Irfan Wani</title>
      <link>https://dev.to/irfan_wani</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/irfan_wani"/>
    <language>en</language>
    <item>
      <title>Sidekick part 5 (finale): what four parts added up to</title>
      <dc:creator>Irfan Wani</dc:creator>
      <pubDate>Wed, 07 Oct 2026 14:06:07 +0000</pubDate>
      <link>https://dev.to/irfan_wani/sidekick-part-5-finale-what-four-parts-added-up-to-2172</link>
      <guid>https://dev.to/irfan_wani/sidekick-part-5-finale-what-four-parts-added-up-to-2172</guid>
      <description>&lt;h1&gt;
  
  
  Sidekick Part 5 (finale): what four parts added up to
&lt;/h1&gt;

&lt;p&gt;This series started with a bet — a terminal companion on your hardware, no account, no bill — and spent four parts defending it piece by piece. This is the last planned part: what it all added up to, what I deliberately left out, and where new parts come from here.&lt;/p&gt;

&lt;h2&gt;
  
  
  The arc in four sentences
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/irfan_wani/sidekick-part-1-a-terminal-companion-on-your-hardware-330k"&gt;Part 1: overview&lt;/a&gt; — &lt;code&gt;sk&lt;/code&gt; is chat, voice, and 19 tools running locally via Ollama, with cloud as opt-in per key rather than structural. The decision: deterministic grounding over prompt instructions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/irfan_wani/sidekick-part-2-grounding-beats-instructions-4607"&gt;Part 2: grounding&lt;/a&gt; — small models ignore rules, so code injects facts before the model sees the prompt: hardware snapshot, auto directory listings, fetched URLs, recency-triggered search. Every past hallucination is locked in &lt;code&gt;test_eval.py&lt;/code&gt; as a regression test.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/irfan_wani/sidekick-part-3-voice-that-never-leaves-the-machine-2o53"&gt;Part 3: voice&lt;/a&gt; — push-to-talk transcribed on your CPU by int8 faster-whisper, recordings deleted after every take. Privacy as architecture: no upload path exists.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/irfan_wani/sidekick-part-4-safety-as-honestly-labeled-ceilings-2o1e"&gt;Part 4: safety&lt;/a&gt; — approvals at dispatch, a deception-assuming shell denylist, egress deny-by-default, one trust chokepoint, and &lt;code&gt;sk audit&lt;/code&gt; as proof. Every ceiling documented with issue numbers instead of oversold.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the parts share
&lt;/h2&gt;

&lt;p&gt;Three through-lines showed up in every installment whether I planned them or not. First, &lt;strong&gt;local-first is load-bearing, not branding&lt;/strong&gt; — grounding works because sysinfo is real, voice privacy holds because there's no server, audit means something because the ledger is yours. Remove "runs on your hardware" and most claims in this series collapse. That's how you test a principle: delete it and see what breaks.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;honesty about ceilings is a feature&lt;/strong&gt;. The series documented guessed caps, dumb regex triggers, an unfenceable shell-curl path, and a 980-line neighbor file (that was another project, but same instinct). Readers trust "here's where it breaks" more than "it just works," and future maintainers — including me — need the ceilings written down more than the victories.&lt;/p&gt;

&lt;p&gt;Third, &lt;strong&gt;everything is sedimentary&lt;/strong&gt;. Grounding exists because instructions failed. Egress rules exist because prompts failed. Eval tests exist because answers failed in production. Nothing here was designed top-down; it accreted around real failures, each layer dated by the bug that forced it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's deliberately missing
&lt;/h2&gt;

&lt;p&gt;Memory and skills, the background daemon, the MCP server beyond a mention, providers and spend caps, the TUI internals. Not oversights — scope control. Each deserves the same treatment (mechanism, tradeoff, breakage) and each gets a part when there's something real to say about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deal going forward
&lt;/h2&gt;

&lt;p&gt;This series is complete as planned. But software doesn't stand still: when new features land, or old ones break interestingly, they'll show up here as new parts — same contract (evidence, mechanism, what broke), same numbering continuing upward. A series that ends is better than one that trails off; a series that resumes for cause is best.&lt;/p&gt;

&lt;p&gt;Thanks for reading all five. Go run it locally: &lt;code&gt;uv tool install sidekick-agent&lt;/code&gt; and &lt;code&gt;sk init&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;Built by the Sidekick community. Repo: &lt;a href="https://github.com/Faisal-Fayaz/sidekick" rel="noopener noreferrer"&gt;https://github.com/Faisal-Fayaz/sidekick&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Sidekick part 4: safety as honestly labeled ceilings</title>
      <dc:creator>Irfan Wani</dc:creator>
      <pubDate>Mon, 05 Oct 2026 05:51:34 +0000</pubDate>
      <link>https://dev.to/irfan_wani/sidekick-part-4-safety-as-honestly-labeled-ceilings-2o1e</link>
      <guid>https://dev.to/irfan_wani/sidekick-part-4-safety-as-honestly-labeled-ceilings-2o1e</guid>
      <description>&lt;h1&gt;
  
  
  Sidekick Part 4: safety as a stack of ceilings, each honestly labeled
&lt;/h1&gt;

&lt;p&gt;Most agent safety I've seen is a paragraph in the system prompt: "be careful with destructive commands." Small models ignore system prompts — we established that in Part 2. So Sidekick's safety model assumes the model will disobey and enforces the boundaries in code instead. This is Part 4: approvals, hard refusals, egress control, and the audit ledger — plus the ceilings, stated rather than hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: approval sets, enforced in the dispatcher
&lt;/h2&gt;

&lt;p&gt;Reads auto-run. Everything else sits in &lt;code&gt;APPROVAL_TOOLS&lt;/code&gt; — writes, deletes, general shell — gated behind inline &lt;code&gt;[y/N]&lt;/code&gt; prompts, with &lt;code&gt;/yolo&lt;/code&gt;, &lt;code&gt;/confirm&lt;/code&gt;, and &lt;code&gt;/readonly&lt;/code&gt; modes. Plan mode and read-only mode go further with deny sets (&lt;code&gt;PLAN_DENIED_TOOLS&lt;/code&gt;, &lt;code&gt;READONLY_DENIED_TOOLS&lt;/code&gt;) enforced at dispatch. The comment in the code says the quiet part out loud: hard gates exist because "prompt text alone does not stop disobedient models." Even &lt;code&gt;exec&lt;/code&gt;, the read-only shell, dies in these modes — inventory commands have no place in a mode that promises zero side effects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: the shell denylist that assumes deception
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;SHELL_BLOCK_PATTERNS&lt;/code&gt; in &lt;code&gt;src/sk/tools/shell.py&lt;/code&gt; reads like a museum of bad days: recursive &lt;code&gt;rm&lt;/code&gt; at &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;~&lt;/code&gt;, &lt;code&gt;$HOME&lt;/code&gt;, bare system roots — matched after quote-stripping, so &lt;code&gt;rm "-rf" /&lt;/code&gt; doesn't slip through. &lt;code&gt;mkfs&lt;/code&gt;, &lt;code&gt;dd&lt;/code&gt; to devices, fork bombs by structural shape, &lt;code&gt;chmod -R 777 /&lt;/code&gt;, &lt;code&gt;curl … | sh&lt;/code&gt;. And crucially, patterns match against de-obfuscated variants, never the raw string alone — because attackers obfuscate and careless users quote.&lt;/p&gt;

&lt;p&gt;My favorite entry: &lt;code&gt;find / -delete&lt;/code&gt;. It destroys trees without any &lt;code&gt;rm&lt;/code&gt; flag, so it sailed past the rm pattern for a while. The comment admits it: already blocked in &lt;code&gt;exec&lt;/code&gt;, but the shell tool had no coverage. Every gap found becomes a pattern; every pattern carries the story of the gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: egress deny-by-default
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/sk/egress.py&lt;/code&gt; splits network traffic into operator-configured (your model endpoint, MCP servers you set up — always allowed) and model-chosen (a page the model decided to read, a URL a provider handed back — the destinations an attacker reaches through prompt injection). The second category is deny-by-default: empty allowlist means no network, wildcards like &lt;code&gt;*&lt;/code&gt; are refused at parse time, and every decision lands in the ledger.&lt;/p&gt;

&lt;p&gt;And then the ceiling, in the module docstring: this covers fetches through the agent's tools. A &lt;code&gt;shell&lt;/code&gt; command running &lt;code&gt;curl&lt;/code&gt; is not covered — "a shell string denylist cannot be a network boundary." The README states it explicitly so the control is not oversold. I respect this enormously: most projects hide their ceilings; this one documents them with issue numbers (#288, #329).&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4: trust fences + the ledger
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;src/sk/trust.py&lt;/code&gt; funnels five independent injection paths (repo docs, web text, search results, &lt;code&gt;@path&lt;/code&gt; inlines, MCP results) through one chokepoint — because a fence applied at only some paths "is not a boundary." Markers are deliberately stable rather than random nonces, since rotating markers would destroy the prompt-cache discipline the test suite locks in; invisible-character smuggling is handled by a sanitizer instead. Same honesty here too: "prompt framing is advisory, not a control" — fences raise the floor; deny rules give the guarantee.&lt;/p&gt;

&lt;p&gt;Everything lands in &lt;code&gt;sk audit&lt;/code&gt;: tool runs, approve/deny decisions, local-vs-egress rows. The ledger is the point where safety becomes proof — the same trail from Part 1's privacy claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;find -delete&lt;/code&gt; gap above. The shell-curl ceiling, which no pattern layer can fix and which is therefore documented instead of patched. And the general lesson across all four parts of this series: each layer exists because the layer above it failed in production — instructions failed, so grounding; refusals failed, so injection; per-tool prompts nagged, so plan review up front. Safety here is sedimentary. That's the whole series: overview, grounding, voice, safety. Memory, skills, and the daemon are future parts if you want them.&lt;/p&gt;




&lt;p&gt;Built by the Sidekick community. Repo: &lt;a href="https://github.com/Faisal-Fayaz/sidekick" rel="noopener noreferrer"&gt;https://github.com/Faisal-Fayaz/sidekick&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Sidekick part 3: voice that never leaves the machine</title>
      <dc:creator>Irfan Wani</dc:creator>
      <pubDate>Sat, 03 Oct 2026 12:24:15 +0000</pubDate>
      <link>https://dev.to/irfan_wani/sidekick-part-3-voice-that-never-leaves-the-machine-2o53</link>
      <guid>https://dev.to/irfan_wani/sidekick-part-3-voice-that-never-leaves-the-machine-2o53</guid>
      <description>&lt;h1&gt;
  
  
  Sidekick Part 3: voice that never leaves the machine
&lt;/h1&gt;

&lt;p&gt;Cloud voice assistants have a dirty secret everyone knows and nobody says: your voice goes to a server. Every "hey assistant" is an upload. Sidekick's &lt;code&gt;sk talk&lt;/code&gt; does the whole loop on your hardware — OS-native capture, local transcription, transcript dropped editable into the prompt. Part 3 of this series is how that pipeline works, and the unglamorous bugs inside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline in 30 seconds
&lt;/h2&gt;

&lt;p&gt;Enter starts recording, Enter stops it. Underneath: the OS-native recorder (arecord/ALSA on Linux, sox/CoreAudio on macOS, ffmpeg fallback) writes 16kHz wav, then local faster-whisper transcribes it — int8 quantized, CPU-only, model downloaded once and cached:&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;if&lt;/span&gt; &lt;span class="n"&gt;model_size&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_model_cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;_model_cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;model_size&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WhisperModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cpu&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;compute_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;int8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_model_cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;model_size&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;segments&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;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transcribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wav_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;beam_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;segments&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;heard only silence — speak louder/closer, or run `sk mic-test` to check levels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No hard dependencies: arecord ships with the OS, sox/ffmpeg come via brew, and faster-whisper installs itself into the running environment on first use (&lt;code&gt;uv pip install faster-whisper&lt;/code&gt;, with a "still not importable — restart and retry" path for the inevitable edge case). In the TUI, &lt;code&gt;ctrl+g&lt;/code&gt; or the mic pill does the same thing. &lt;code&gt;--stt-model base&lt;/code&gt; trades accuracy for speed when words vanish.&lt;/p&gt;

&lt;h2&gt;
  
  
  One technical decision: errors written for the speaker, not the log
&lt;/h2&gt;

&lt;p&gt;Look at the failure messages: "recording is nearly empty — mic may be muted, run &lt;code&gt;sk mic-test&lt;/code&gt;." "Heard only silence — speak louder/closer." Each one names the likely physical cause and the next command. That came from watching real sessions: transcription failures are almost never model failures, they're room failures — muted mics, wrong devices, whisper-quiet laptops. So &lt;code&gt;sk mic-test&lt;/code&gt; records three seconds and returns a verdict — silent, quiet, good — with peak dB and an alsamixer hint when it's the OS mixer lying to you.&lt;/p&gt;

&lt;p&gt;The privacy side is structural, not promised. Recordings are temp files deleted after every take — there's nowhere for them to accumulate because nothing in the pipeline has a server to send them to. The strongest privacy guarantee is an architecture with no upload path.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke
&lt;/h2&gt;

&lt;p&gt;Two favorites from &lt;code&gt;src/sk/voice.py&lt;/code&gt;. First: Python 3.13 removed &lt;code&gt;audioop&lt;/code&gt;, so mic-level measurement needed a hand-rolled PCM stats function with &lt;code&gt;struct&lt;/code&gt; — including a comment about endianness placement in format strings that reads like someone lost an afternoon to it.&lt;/p&gt;

&lt;p&gt;Second, the spawn guard. faster-whisper's dependency tree can start a &lt;code&gt;multiprocessing&lt;/code&gt; child, and on macOS recycled file descriptors made CPython reject the spawn with &lt;code&gt;ValueError: bad value(s) in fds_to_keep&lt;/code&gt;. The fix monkeypatches &lt;code&gt;spawnv_passfds&lt;/code&gt; to sanitize the fd list — dedupe, sort, drop closed ones — installed once and only around STT work. And because callers reduce failures to one-line messages, which hid the genuine crash site, full tracebacks now append to &lt;code&gt;~/.sidekick/voice-errors.log&lt;/code&gt;. The bug that teaches you your error reporting is broken is always two bugs.&lt;/p&gt;

&lt;p&gt;Part 4 goes into the tool safety model: 19 tools, approval gates, hard refusals, and the audit ledger.&lt;/p&gt;




&lt;p&gt;Built by the Sidekick community. Repo: &lt;a href="https://github.com/Faisal-Fayaz/sidekick" rel="noopener noreferrer"&gt;https://github.com/Faisal-Fayaz/sidekick&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Sidekick part 2: grounding beats instructions</title>
      <dc:creator>Irfan Wani</dc:creator>
      <pubDate>Fri, 02 Oct 2026 05:04:34 +0000</pubDate>
      <link>https://dev.to/irfan_wani/sidekick-part-2-grounding-beats-instructions-4607</link>
      <guid>https://dev.to/irfan_wani/sidekick-part-2-grounding-beats-instructions-4607</guid>
      <description>&lt;h1&gt;
  
  
  Sidekick Part 2: grounding beats instructions — facts injected before the model sees the prompt
&lt;/h1&gt;

&lt;p&gt;Small local models have a habit nobody warns you about: they ignore system-prompt rules. Tell a 3B model "always check the hardware before recommending," and it will confidently recommend anyway — from vibes. Our eval harness still carries the scar: an early version answered a hardware question with generic LLM advice, scoring 3/10 on our own quality check. Another told a user their &lt;code&gt;~/neural-hangar&lt;/code&gt; directory "does not exist." It existed. A third refused to fetch a URL at all: "I can't browse."&lt;/p&gt;

&lt;p&gt;Three failures, one root cause: we were asking the model to go get facts. In Part 2 of this series, here's the design that fixed it — deterministic grounding in &lt;code&gt;src/sk/agent.py&lt;/code&gt;, where code resolves facts and injects them before the model ever sees the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four injectors
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Real system snapshot, every turn.&lt;/strong&gt; The system prompt carries a live hardware snapshot — CPU, RAM, GPU, disk — rendered fresh per turn. A mandatory rule sits next to it: if the question is about the user's device, the model MUST still call &lt;code&gt;sysinfo&lt;/code&gt; so the trace shows grounding. Belt and suspenders: injected facts for correctness, a tool call for an auditable trail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Auto local context.&lt;/strong&gt; If your message mentions &lt;code&gt;~/anything&lt;/code&gt;, a regex pass lists that directory and reads its &lt;code&gt;package.json&lt;/code&gt;/&lt;code&gt;README.md&lt;/code&gt;/&lt;code&gt;pyproject.toml&lt;/code&gt; — capped at 3 paths, ~2000 chars each:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_auto_local_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Deterministic grounding: if user mentions ~/X, list it + read manifests.
    This does NOT rely on the model calling tools — it injects facts so the
    model cannot hallucinate &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;does not exist&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="n"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(~/[\w\-./~]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;listing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tool_list_dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[path &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;listing&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;]&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model never gets the chance to say "does not exist" — the listing is already in context. This directly killed the &lt;code&gt;~/neural-hangar&lt;/code&gt; hallucination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Auto web context.&lt;/strong&gt; Same idea for URLs: up to 2 URLs fetched, 3000 chars each — a deliberate cap protecting the 8k context window of small models. The "I can't fetch URLs" refusal died here, backed by a never-refuse rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Auto search context.&lt;/strong&gt; Explicit "search the web for X" phrasing triggers a search whose top-5 hits get injected. More interesting: recency markers ("latest," "right now," "this week") trigger it automatically — unless the question is local (mentions &lt;code&gt;~/&lt;/code&gt;, "my device," "can I run"), in which case search stays out of the way. That local-vs-world branch matters: searching the web for "can I run this on my GPU" would actively make the answer worse.&lt;/p&gt;

&lt;p&gt;Two guardrails wrap all of it. &lt;strong&gt;Tool honesty&lt;/strong&gt;: the model may report results ONLY from &lt;code&gt;tool&lt;/code&gt;-role messages — never invent search results, file contents, or prices, and say plainly when it didn't call the tool. &lt;strong&gt;Untrusted fences&lt;/strong&gt;: anything captured from outside (files, web pages, search results) arrives in fenced blocks marked DATA, never instructions — repo docs can teach conventions, but nothing in a fence can order a command, read a secret, or exfiltrate. That's the prompt-injection defense, structural rather than wished-for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke (the eval receipts)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;tests/test_eval.py&lt;/code&gt; opens with a comment listing real bad answers seen live, each now a locked regression test — 40 and counting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3/10 generic advice with no grounding → the sysinfo + never-guess rules&lt;/li&gt;
&lt;li&gt;"does not exist" hallucination → auto local facts&lt;/li&gt;
&lt;li&gt;"I can't fetch URLs" refusal → auto web facts + never-refuse rule&lt;/li&gt;
&lt;li&gt;silent write execution → the approval gate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern: every quality failure became an offline test asserting on prompt assembly, tools, and gates — no Ollama needed. Quality bugs you can reproduce in CI stay fixed; ones you can't, don't.&lt;/p&gt;

&lt;p&gt;The honest limitation: caps are guesses. Three paths, two URLs, 3000 chars — tuned for 8k-context models, not derived from anything. On larger-context models we're likely under-feeding. And regex triggers are dumb by design: &lt;code&gt;~/&lt;/code&gt; in a joke about home directories still lists a directory. Cheap, predictable, occasionally silly — I'll take that over clever and surprising in the prompt path.&lt;/p&gt;

&lt;p&gt;Part 3 goes into the voice pipeline: OS-native capture, local faster-whisper transcription, and why recordings are temp files deleted after every take.&lt;/p&gt;




&lt;p&gt;Built by the Sidekick community. Repo: &lt;a href="https://github.com/Faisal-Fayaz/sidekick" rel="noopener noreferrer"&gt;https://github.com/Faisal-Fayaz/sidekick&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Sidekick part 1: a terminal companion on your hardware</title>
      <dc:creator>Irfan Wani</dc:creator>
      <pubDate>Thu, 01 Oct 2026 04:51:43 +0000</pubDate>
      <link>https://dev.to/irfan_wani/sidekick-part-1-a-terminal-companion-on-your-hardware-330k</link>
      <guid>https://dev.to/irfan_wani/sidekick-part-1-a-terminal-companion-on-your-hardware-330k</guid>
      <description>&lt;h1&gt;
  
  
  Sidekick: a terminal companion that runs on your hardware (Part 1: overview)
&lt;/h1&gt;

&lt;p&gt;Every AI coding assistant I used had the same deal: create an account, pipe your code through someone's cloud, watch the meter run. The models kept getting better, but the shape never changed — your files leave your machine, always.&lt;/p&gt;

&lt;p&gt;Sidekick is the opposite bet: a local-first terminal companion you talk to — chat, voice, and 19 tools — running on your own hardware. No cloud account required, no API bill by default. Your files, memory, and voice never leave your machine unless you hand it a key. This is Part 1 of a series: the overview. Later parts go deep on grounding, voice, and the tool safety model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually is
&lt;/h2&gt;

&lt;p&gt;One command, &lt;code&gt;sk&lt;/code&gt;, with several surfaces sharing everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fullscreen TUI&lt;/strong&gt; (&lt;code&gt;sk&lt;/code&gt;) — streaming Markdown answers, slash-command autocomplete with fuzzy filtering, sessions drawer, model picker, themes, push-to-talk mic pill&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plain REPL&lt;/strong&gt; (&lt;code&gt;sk chat&lt;/code&gt;) — for dumb terminals, screen readers, broken TUIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice&lt;/strong&gt; (&lt;code&gt;sk talk&lt;/code&gt;) — Enter to record, Enter to stop; transcribed on your CPU by local faster-whisper, never uploaded&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-shot runs&lt;/strong&gt; (&lt;code&gt;sk run "task"&lt;/code&gt;) — scriptable agent runs with &lt;code&gt;--json&lt;/code&gt;, &lt;code&gt;--plan&lt;/code&gt;, &lt;code&gt;--bg&lt;/code&gt; for background jobs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP server&lt;/strong&gt; (&lt;code&gt;sk mcp&lt;/code&gt;) — all 19 tools over stdio, so Claude Desktop and friends can use it too&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real captures from the app running headless — slash autocomplete, then a grounded answer with live system data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkxvchizy13g6q3565vh4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkxvchizy13g6q3565vh4.png" alt="Slash autocomplete" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwh2ad10asgq5vx976ker.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwh2ad10asgq5vx976ker.png" alt="Grounded answer with real system data" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sessions, compacting, and review all work across surfaces:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5nhw545q1x9c72jtc6js.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5nhw545q1x9c72jtc6js.png" alt="Sessions drawer" width="800" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Underneath: long-term memory with full-text search, todos, shell history, background daemon with calendar schedules, skill packs in &lt;code&gt;SKILL.md&lt;/code&gt; format — and an audit ledger (&lt;code&gt;sk audit&lt;/code&gt;) that logs every tool run, approval, and network egress so you can &lt;em&gt;prove&lt;/em&gt; your code never leaked. That last one is the point most cloud tools structurally can't match: their product is your code; ours never sees it.&lt;/p&gt;

&lt;h2&gt;
  
  
  One technical decision: grounding beats instructions
&lt;/h2&gt;

&lt;p&gt;The design bet I'd defend: &lt;strong&gt;deterministic grounding injected before the model sees the prompt&lt;/strong&gt; — your paths, sysinfo, URLs, resolved as code, not described in prose. Small local models routinely ignore system-prompt rules, but they can't argue with facts already in context. Same instinct elsewhere: FTS5 full-text search over vector embeddings (zero dependencies, instant, no embedding server eating RAM on a 4GB box), and capability profiles where known models declare their tool protocol instead of paying a probing round-trip to discover it.&lt;/p&gt;

&lt;p&gt;Bring your own key when you want frontier models — one guided &lt;code&gt;sk connect&lt;/code&gt; flow, a dozen providers plus any OpenAI-compatible endpoint, spend caps per session, keys chmod 600 and masked. Local stays the default; cloud is opt-in per key, never structural.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety is a feature, not a banner
&lt;/h2&gt;

&lt;p&gt;Reads auto-run. Writes, deletes, and shell need approval; &lt;code&gt;shell&lt;/code&gt; hard-refuses &lt;code&gt;rm -rf /&lt;/code&gt;, &lt;code&gt;mkfs&lt;/code&gt;, device writes, and fork bombs even if you approve them. URL fetching blocks localhost and private IPs. Multi-tool turns with destructive actions get one plan review up front instead of nag-per-tool. Plan mode (&lt;code&gt;/plan&lt;/code&gt;, &lt;code&gt;F4&lt;/code&gt;) blocks writes entirely for research sessions, and &lt;code&gt;/rewind&lt;/code&gt; undoes agent file edits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv tool &lt;span class="nb"&gt;install &lt;/span&gt;sidekick-agent[voice]  &lt;span class="c"&gt;# global `sk`, STT included&lt;/span&gt;
sk init     &lt;span class="c"&gt;# guided first-run: hardware → model → verify&lt;/span&gt;
sk          &lt;span class="c"&gt;# fullscreen chat — start here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Local path needs Ollama (&lt;code&gt;ollama serve&lt;/code&gt;, pull &lt;code&gt;qwen2.5-coder:7b&lt;/code&gt; for smarts or &lt;code&gt;llama3.2:3b&lt;/code&gt; for speed). 834 tests pass with no Ollama needed. In Part 2 I'll go inside the grounding pipeline — what gets injected, when, and what broke along the way.&lt;/p&gt;




&lt;p&gt;Built by the Sidekick community. Repo: &lt;a href="https://github.com/Faisal-Fayaz/sidekick" rel="noopener noreferrer"&gt;https://github.com/Faisal-Fayaz/sidekick&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I built a 3D system design simulator to stop hand-waving architectures</title>
      <dc:creator>Irfan Wani</dc:creator>
      <pubDate>Tue, 29 Sep 2026 17:29:31 +0000</pubDate>
      <link>https://dev.to/irfan_wani/i-built-a-3d-system-design-simulator-to-stop-hand-waving-architectures-4nd6</link>
      <guid>https://dev.to/irfan_wani/i-built-a-3d-system-design-simulator-to-stop-hand-waving-architectures-4nd6</guid>
      <description>&lt;h1&gt;
  
  
  I built a 3D system design simulator to stop hand-waving architectures
&lt;/h1&gt;

&lt;p&gt;System design interviews and architecture reviews share a problem: we draw boxes and arrows on a whiteboard and assert things like "the cache absorbs most of this" or "we'll just auto-scale." Nobody checks. I wanted a toy where you can actually watch those claims break — so I built one.&lt;/p&gt;

&lt;p&gt;Give it a try: &lt;a href="https://system-design-simulator-navy.vercel.app/" rel="noopener noreferrer"&gt;https://system-design-simulator-navy.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;System Design 3D Simulator is a web app: you compose infrastructure out of components (DNS, CDN, load balancers, rate limiters, API gateways, app servers, WebSocket gateways, databases with shardable primaries, read replicas, caches, search clusters, queues, workers, object storage, monitoring), wire them together in a 3D scene, dial up traffic, and watch throughput, p50/p99 latency, error rate, availability, queue depth, and cost respond in real time.&lt;/p&gt;

&lt;p&gt;It also ships one-click interview architectures — URL shortener, social feed with fan-out on write, chat with WebSockets, CDN-heavy video platform — plus demand patterns (steady, viral spikes, daily cycle, growth ramp) so you can capacity-plan for peaks instead of averages.&lt;/p&gt;

&lt;p&gt;The stack is React 19 + Vite 8 + Three.js via React Three Fiber, with Zustand holding all simulation state and Lucide for icons. Four files do most of the work: &lt;code&gt;store.js&lt;/code&gt; (881 lines — the whole simulation engine), &lt;code&gt;Scene.jsx&lt;/code&gt; (980 lines — 3D models, failure visuals, packet animation), &lt;code&gt;ControlPanel.jsx&lt;/code&gt; (388 lines), and &lt;code&gt;componentInfo.js&lt;/code&gt; (458 lines).&lt;/p&gt;

&lt;h2&gt;
  
  
  One technical decision: a tick-based capacity model with magic numbers up front
&lt;/h2&gt;

&lt;p&gt;The core of the simulation is a fixed-tick loop (&lt;code&gt;TICK_S = 0.8&lt;/code&gt; seconds simulated per tick) where every component type gets a per-node capacity constant:&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;APP_RPS_PER_NODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;150&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;LC_EFFICIENCY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.08&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// least-connections spreads better&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CDN_OFFLOAD_PCT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.6&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;DB_LOAD_CAP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// load units per primary before saturation&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CB_DROP_FRACTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.35&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// requests shed while breaker is open&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MQ_BUFFER_CAP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// buffered msgs per broker before drops&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WORKER_DRAIN_PER_NODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tradeoff is obvious: these numbers are invented, not measured. A real CDN offloads far more than 60% for cacheable content; a real app server's RPS depends on what the handler does. But the point of the tool was never absolute accuracy — it's relative behavior. Kill the only load balancer and you get a total outage. Add read replicas and the primary stops saturating. Switch the LB strategy from round robin to least-connections (that 1.08 efficiency factor) or consistent hashing and you can watch the hot-partition trap appear. The constants being rough doesn't matter as long as the &lt;em&gt;relationships&lt;/em&gt; between components behave plausibly, and keeping them as named constants in one place means anyone can tune them toward realism later.&lt;/p&gt;

&lt;p&gt;The same thinking applies to the cost model: every component has a monthly $/node figure, cost per 1M requests is derived live — and dead nodes still bill. That last detail is deliberate. Idle capacity costing money is the whole argument for auto-scaling, and the simulator lets you toggle auto-scaling and watch scaling events fire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chaos was the fun part
&lt;/h2&gt;

&lt;p&gt;Each node accumulates damage under sustained overload and eventually crashes; auto-heal restarts it, modeled loosely on Kubernetes liveness probes. A circuit breaker opens when the primary DB saturates and sheds read pressure until recovery. You can kill any node by hand, or roll the chaos monkey. The failure I keep coming back to: search queries with no search cluster fall back to expensive scans on the app servers and DB, and p99 quietly degrades instead of erroring. Silent degradation is harder to notice than an outage, which is exactly why I kept it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke / what's next
&lt;/h2&gt;

&lt;p&gt;Honest status: the failure visuals and packet animation live in the same 980-line &lt;code&gt;Scene.jsx&lt;/code&gt; as the component models, and that file is the hardest thing in the repo to change — it needs splitting before anything else. The 3D drag-and-drop interaction (added a few commits ago along with the info modals) still feels fiddly for precise topologies. And the capacity constants, as noted, are placeholders with opinions — calibrating even one path (say, queue drain behavior) against a real benchmark would make the whole thing more trustworthy.&lt;/p&gt;

&lt;p&gt;Next up would be per-link latency visualization and a shareable-URL encoding of a topology so two people can argue about the same diagram.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then pick the URL shortener scenario, switch demand to viral spike, kill a node, and watch what redundancy actually buys you. That's the whole pitch: fewer whiteboard assertions, more watching things break.&lt;/p&gt;

&lt;h2&gt;
  
  
  In action
&lt;/h2&gt;

&lt;p&gt;Real captures from the running app — a healthy system at 850/850 rps, p99 at 302ms:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdtwims59mwhy5db8xzz7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdtwims59mwhy5db8xzz7.png" alt="Healthy system with live metrics" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwidwmyd0oc4t6k25hc38.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwidwmyd0oc4t6k25hc38.png" alt="3D topology mid-interaction" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7m3o6xwm649lz7kzkdeu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7m3o6xwm649lz7kzkdeu.png" alt="Metrics panel under load" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Built by Irfan Wani. Repo: &lt;a href="https://github.com/Irfanwani/system-design-simulator" rel="noopener noreferrer"&gt;https://github.com/Irfanwani/system-design-simulator&lt;/a&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>react</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
