<?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: EazyHood</title>
    <description>The latest articles on DEV Community by EazyHood (@eazyhood).</description>
    <link>https://dev.to/eazyhood</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%2F4059300%2Fc073d3f1-31e0-436b-a478-e94296e9d9cc.png</url>
      <title>DEV Community: EazyHood</title>
      <link>https://dev.to/eazyhood</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eazyhood"/>
    <language>en</language>
    <item>
      <title>SweepSheet: turn cleanup voice notes into reviewed counts</title>
      <dc:creator>EazyHood</dc:creator>
      <pubDate>Wed, 07 Oct 2026 14:26:23 +0000</pubDate>
      <link>https://dev.to/eazyhood/sweepsheet-turn-cleanup-voice-notes-into-reviewed-counts-2f29</link>
      <guid>https://dev.to/eazyhood/sweepsheet-turn-cleanup-voice-notes-into-reviewed-counts-2f29</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;A cleanup has two different jobs: picking things up and keeping track of what was collected. SweepSheet puts the bookkeeping after the outdoor part.&lt;/p&gt;

&lt;p&gt;Use your usual voice recorder to leave a short note: “Three plastic bottles and two cans.” Put the phone away. When you’re back, import the recording into SweepSheet. An open speech model transcribes it on your device, and a small parser proposes quantities in six categories. Listen, correct anything necessary, and confirm. Only then do the numbers enter your notebook and CSV.&lt;/p&gt;

&lt;p&gt;The person I designed for is someone keeping the tally for a small cleanup, with several voice memos to turn into a table. This is a working prototype for that task, not a report of a cleanup I have already conducted. The examples are synthetic speech and are labeled that way throughout.&lt;/p&gt;

&lt;p&gt;The most revealing test was a mistake. The sentence “Twenty three wrappers and twelve cans” came back as &lt;strong&gt;“23 rappers and 12 kids.”&lt;/strong&gt; SweepSheet did not try to rescue it with another model. It left the counts for a person to enter after listening.&lt;/p&gt;

&lt;p&gt;That decision shapes the whole application: speech recognition can help with transcription, but it does not get to approve the record.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://eazyhood.github.io/sweepsheet/" rel="noopener noreferrer"&gt;Open SweepSheet&lt;/a&gt;&lt;/strong&gt; — no account or API key needed.&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%2F77hx9rkrk2b6vuclcn4p.jpg" 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%2F77hx9rkrk2b6vuclcn4p.jpg" alt="SweepSheet in Edge after real local transcription and confirmation of the synthetic canal note: three plastic bottles, two cans, total five." width="800" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A real browser run with a clearly marked synthetic recording. The total is five because one note has been confirmed; the other two are still pending.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Choose &lt;strong&gt;Try the example notebook&lt;/strong&gt;, then open &lt;strong&gt;Along the canal&lt;/strong&gt;. The initial example transcript comes from the saved model test. &lt;strong&gt;Transcribe again&lt;/strong&gt; runs Whisper on your own device; the first use downloads the model.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Play the note and check &lt;strong&gt;3 plastic bottles + 2 cans&lt;/strong&gt;. Confirm: the total becomes &lt;strong&gt;5&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Review &lt;strong&gt;By the footbridge&lt;/strong&gt;: &lt;strong&gt;4 wrappers + 6 cigarette butts + 1 glass bottle&lt;/strong&gt;. Confirm: &lt;strong&gt;16&lt;/strong&gt; in total.&lt;/li&gt;
&lt;li&gt;Open &lt;strong&gt;An uncertain count&lt;/strong&gt;. Its transcript says “Maybe 4 or 5 rappers.” The app holds the proposal; it adds nothing.&lt;/li&gt;
&lt;li&gt;Correct a confirmed quantity and explain why. The total changes by replacement, while the previous quantities remain in the review history.&lt;/li&gt;
&lt;li&gt;Download the confirmed CSV and the JSON review history. The latter includes the audio file’s SHA-256 identifier, original model output and corrections, but not the audio bytes.&lt;/li&gt;
&lt;/ol&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%2Fuvnl7q7lld2n9m0xebcp.jpg" 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%2Fuvnl7q7lld2n9m0xebcp.jpg" alt="The uncertain synthetic recording is held for review, with zero suggested counts and the previously confirmed total unchanged." width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Uncertainty stays visible. Opening this note does not change the confirmed total.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Your own short audio files work too. Keep them below 60 seconds and 10 MB. A printable field card lists the six categories and a short recording example. There is no requirement to keep this app open outdoors or rely on a web recorder with a locked phone.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/EazyHood/sweepsheet" rel="noopener noreferrer"&gt;Source, setup and evidence on GitHub&lt;/a&gt;&lt;/strong&gt; · &lt;a href="https://github.com/EazyHood/sweepsheet/blob/main/evidence/model-run.json" rel="noopener noreferrer"&gt;All ten test inputs and observed outputs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The app and repository were started on October 7 for this challenge. The application code is MIT licensed. Model and library licenses are credited separately in the README.&lt;/p&gt;

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

&lt;p&gt;The pipeline is small enough to inspect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Audio file → browser decoding → Whisper tiny.en in a worker
           → editable transcript → conservative count proposal
           → explicit human confirmation → totals and CSV
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used Transformers.js 3.8.1 and the quantized &lt;code&gt;Xenova/whisper-tiny.en&lt;/code&gt; ONNX conversion, pinned to revision &lt;code&gt;79fb389fc764e7c395bd330e9531d9d32ada7049&lt;/code&gt;. The browser runs inference through ONNX Runtime WASM. A worker keeps that computation away from the interface. The app stores recordings, transcripts and review history in IndexedDB; there is no transcription server or application backend.&lt;/p&gt;

&lt;p&gt;The parser supports exact quantities in six categories. It holds corrections, vague amounts, negation and unsupported numbered categories. “Bottles” alone needs a material. The parser does not silently repair “rappers” to “wrappers.” These are inspectable, intentionally limited rules, not a claim to understand every sentence.&lt;/p&gt;

&lt;p&gt;Every imported note starts pending. Exact-byte reimports are detected with SHA-256, so importing the same file twice does not double the tally. Corrections replace a note’s quantities instead of adding them again. Excluded notes remain available for reference. The CSV contains confirmed records only; the JSON also retains pending and excluded notes. Spreadsheet-sensitive text is escaped.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the tests actually showed
&lt;/h3&gt;

&lt;p&gt;I generated eight short spoken fixtures locally with Microsoft Zira Desktop, plus digital silence and deterministic white noise. The &lt;a href="https://github.com/EazyHood/sweepsheet/blob/main/evidence/README.md" rel="noopener noreferrer"&gt;full method&lt;/a&gt; distinguishes synthetic speech, CPU inference and browser execution.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test group&lt;/th&gt;
&lt;th&gt;Observed result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Four unambiguous count sentences&lt;/td&gt;
&lt;td&gt;Three produced the intended quantities; one was held after a recognition failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Four correction, uncertain, vague or not-collected sentences&lt;/td&gt;
&lt;td&gt;All held for manual review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silence and white noise&lt;/td&gt;
&lt;td&gt;Neither proposed counts; noise was transcribed as “(water splashing)”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The noise result matters too: even a speech model can invent a plausible description. Nothing about that output establishes that there was water, much less collected litter.&lt;/p&gt;

&lt;p&gt;The tests also check that pending and excluded notes never affect totals, corrections retain the earlier values, invalid quantities are rejected, and CSV text cannot become a spreadsheet formula. These checks protect the recording workflow; they do not establish outdoor ASR accuracy.&lt;/p&gt;

&lt;p&gt;The current limits are practical. It is English-only. Browser audio-format support varies. Model preparation needs internet, and offline page reload is not supported in this version. Browser storage can be cleared, so keep original recordings and export your work. Wind, real voices, accents and mobile performance still need field testing. I have not measured a reduction in screen time or claimed environmental impact from these example counts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI assistance:&lt;/strong&gt; Codex substantially assisted with implementation, tests, documentation and this write-up. The synthetic demo voice is disclosed separately. No field experience, interviews or user metrics have been invented to fill in the story.&lt;/p&gt;

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

&lt;p&gt;The useful open component here is the transcription model, not a logo beside a text field. Without it, the user must type the recording into the notebook. With it, audio can be transcribed on the same device, without uploading cleanup recordings or obtaining an inference API key.&lt;/p&gt;

&lt;p&gt;The pinned weights and readable pipeline also make the failure above reproducible. Someone can try another open speech model against the same WAV files, or adapt the parser to another cleanup vocabulary, while keeping the confirmation rules intact. The application does not depend on a remote provider continuing to offer a particular model or pricing tier.&lt;/p&gt;

&lt;p&gt;A closed API could also transcribe these notes. The reason to use open weights is control over where inference happens and the ability to inspect and replace the component. It does not make recognition magically reliable.&lt;/p&gt;

&lt;p&gt;Cleanup logging itself already exists—&lt;a href="https://oceanconservancy.org/work/plastics/cleanups-icc/clean-swell-app/" rel="noopener noreferrer"&gt;Clean Swell&lt;/a&gt; is one example—and a voice memo plus a spreadsheet is a reasonable manual alternative. SweepSheet’s contribution is narrower: keep the recording beside a proposed tally, make uncertainty visible, and preserve what changed after review. The next useful test is taking that complete workflow to a real cleanup and finding out whether the bookkeeping is actually easier.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>A mano: remember the place, find the thing</title>
      <dc:creator>EazyHood</dc:creator>
      <pubDate>Fri, 02 Oct 2026 13:48:00 +0000</pubDate>
      <link>https://dev.to/eazyhood/a-mano-remember-the-place-find-the-thing-115</link>
      <guid>https://dev.to/eazyhood/a-mano-remember-the-place-find-the-thing-115</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I built &lt;strong&gt;A mano&lt;/strong&gt; around a practical problem affecting Lucía, a designer who works from home. &lt;strong&gt;Lucía is a pseudonym for a real person who prefers to keep her identity private.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;She sometimes remembers what an object does more easily than its name or where she stored it. Connecting a laptop to a television becomes a search for the thing that puts the computer screen on the TV, rather than for “USB-C to HDMI.”&lt;/p&gt;

&lt;p&gt;A mano connects that remembered purpose to a saved record. Each object has a name, room, precise location and optional description. Ordinary keyword search works immediately. An optional open-weight model runs in the browser and finds additional matches from descriptions of what someone needs to do. It suggests existing records; it cannot invent locations.&lt;/p&gt;

&lt;p&gt;The useful result is deliberately simple: an editable card saying where to look. Users can update a location after moving an object, mark favorites, filter rooms and export a backup.&lt;/p&gt;

&lt;p&gt;The public demo contains ten synthetic records, separate from personal collections. Its adapter example reproduces a technically tested search; it is not evidence of Lucía's possessions, feedback or measured time savings.&lt;/p&gt;

&lt;p&gt;Personal records stay in the browser, without synchronization or encryption. The model needs an initial download and can return incorrect suggestions. Objects must first be recorded, and their locations kept current. Those limits remain visible alongside the working demo.&lt;/p&gt;

&lt;p&gt;The Spanish interface uses a compact catalogue layout, self-hosted Archivo and original SVG objects. Card transitions preserve context when filtering, while keyboard navigation and reduced-motion preferences remain supported.&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%2Frovqdhl69adqsgh77x4y.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%2Frovqdhl69adqsgh77x4y.png" alt="The A mano interface, showing synthetic demonstration objects" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://eazyhood.github.io/a-mano/" rel="noopener noreferrer"&gt;Open A mano&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Try this route:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep &lt;strong&gt;Explorar demo&lt;/strong&gt; selected. Search &lt;code&gt;estuche gris&lt;/code&gt;; the adapter appears because that exact place is recorded.&lt;/li&gt;
&lt;li&gt;Clear the field and choose &lt;strong&gt;Activar&lt;/strong&gt;. The first activation downloads approximately 160 MB of public model and runtime assets. No account or paid inference key is needed.&lt;/li&gt;
&lt;li&gt;Search &lt;code&gt;quiero mostrar la pantalla del computador en el televisor&lt;/code&gt;. The model suggests the USB-C to HDMI adapter even though that sentence is not its title. Open its card to inspect the recorded location.&lt;/li&gt;
&lt;li&gt;Switch to &lt;strong&gt;Mis cosas&lt;/strong&gt;, add a harmless test object, edit its place and export a backup. The fictional and personal collections stay separate.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;&lt;a href="https://github.com/EazyHood/a-mano" rel="noopener noreferrer"&gt;Repository, reproducible checks and evidence&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is new source written during the event window, with OpenAI Codex assistance. Earlier projects informed design decisions; their code was not repackaged as this entry. The repository records authorship, third-party licenses, synthetic test data and current limitations.&lt;/p&gt;

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

&lt;p&gt;React and TypeScript handle the interface. Transformers.js 3.8.1 loads a pinned, quantized ONNX conversion of &lt;strong&gt;paraphrase-multilingual-MiniLM-L12-v2&lt;/strong&gt; in a Web Worker. Mean-pooled, normalized embeddings let cosine similarity rank existing names, descriptions and tags. Literal matches are retained and placed first, so activating AI does not make exact locations disappear.&lt;/p&gt;

&lt;p&gt;The asynchronous details matter: stale queries and collection versions are ignored, clearing a query restores the ready state, and room filters operate on all qualifying candidates rather than a prematurely shortened result list. The model cannot write or delete records.&lt;/p&gt;

&lt;p&gt;The app passed 55 unit tests, production compilation and browser checks for add/edit/favorite/reload, export/delete/restore, narrow layouts and actual ONNX inference. A loaded session could also search with the network disabled. This does not establish offline page reload support.&lt;/p&gt;

&lt;p&gt;In eleven synthetic target queries against the ten-object demo, literal search found two target records and the hybrid approach found nine. Two absent-object examples produced no result. The model still missed an Allen-key paraphrase and a sofa-measuring paraphrase. A second open model and a fragmented index did not improve the tradeoff, so neither was shipped. These are small reproducible examples, not a user study or an accuracy guarantee. &lt;a href="https://github.com/EazyHood/a-mano/blob/main/docs/ai-evaluation.md" rel="noopener noreferrer"&gt;Read the evaluation and its failures&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Open weights and a browser runtime let the semantic search happen on the device without sending household descriptions to a hosted inference API. They also make it possible to pin the model, compare alternatives and publish the actual evaluation, including failures.&lt;/p&gt;

&lt;p&gt;That choice has costs: a noticeable first download, device computation and imperfect suggestions. Browser storage is not encrypted or synchronized, and the hosting services still receive ordinary asset requests. The design makes those boundaries visible instead of implying that local inference solves every privacy or reliability problem.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>The cache key that only looked at one of its four inputs</title>
      <dc:creator>EazyHood</dc:creator>
      <pubDate>Sun, 02 Aug 2026 15:47:59 +0000</pubDate>
      <link>https://dev.to/eazyhood/the-cache-key-that-ignored-the-question-2g59</link>
      <guid>https://dev.to/eazyhood/the-cache-key-that-ignored-the-question-2g59</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5xudapwnqmzh9vdxl63n.jpg" 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%2F5xudapwnqmzh9vdxl63n.jpg" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Paritok-official/paritok-4b-v1" rel="noopener noreferrer"&gt;Paritok&lt;/a&gt; is a context compressor. You&lt;br&gt;
hand it a blob of text, a query describing what you care about, and a compression level,&lt;br&gt;
and it hands back a shorter version that is supposed to keep what matters &lt;em&gt;for that query&lt;/em&gt;&lt;br&gt;
and drop the rest. Results are cached, because compression is the expensive part.&lt;/p&gt;

&lt;p&gt;I was not looking for bugs in it. I was building an audit harness to measure how much&lt;br&gt;
&lt;em&gt;meaning&lt;/em&gt; compression costs — feeding the same corpus through with different queries and&lt;br&gt;
checking which critical substrings survive: file paths, error types, line numbers,&lt;br&gt;
identifiers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;Two rows in my results table were byte-for-byte identical. Different query, different&lt;br&gt;
compression level, same 544 characters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;query="Fix the IntegrityError on commit"              level=L0  -&amp;gt;  159 tok  cache_hit=False
query="Explain the tax rounding TODO in compute_tax"  level=L3  -&amp;gt;  159 tok  cache_hit=True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;compress()&lt;/code&gt; takes four things that shape its output — &lt;code&gt;content&lt;/code&gt;, &lt;code&gt;query&lt;/code&gt;, &lt;code&gt;level&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;kind&lt;/code&gt;. The cache was keyed on one of them:&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;sid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;content_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_cached_compressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the first caller's answer was served to every later caller with the same input text, no&lt;br&gt;
matter what they actually asked. The second caller got a compression tuned to somebody&lt;br&gt;
else's question, marked &lt;code&gt;cache_hit=True&lt;/code&gt;, with nothing in the response to say so.&lt;/p&gt;

&lt;p&gt;This is worse than a stale cache. A stale cache gives you an old answer to &lt;em&gt;your&lt;/em&gt; question.&lt;br&gt;
This gives you a fresh answer to &lt;em&gt;someone else's&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The fix is open as a PR: &lt;strong&gt;&lt;a href="https://github.com/Paritok-official/paritok-4b-v1/pull/17" rel="noopener noreferrer"&gt;Paritok-official/paritok-4b-v1#17&lt;/a&gt;&lt;/strong&gt; — 2 files, +118/−12.&lt;/p&gt;

&lt;p&gt;The audit harness that surfaced it is at&lt;br&gt;
&lt;a href="https://github.com/EazyHood/paritok-audit" rel="noopener noreferrer"&gt;EazyHood/paritok-audit&lt;/a&gt; (Apache-2.0).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The compressed result depends on query, level and kind as well as on the
# content -- query is documented as driving keep/drop -- so all four belong
# in the key. Keying on content alone returned the first caller's answer to
# every later caller. `sid` deliberately stays content-only, because
# expand_context resolves originals by content and that behaviour is correct.
&lt;/span&gt;&lt;span class="n"&gt;cache_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;content_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x00&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;sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="ow"&gt;or&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;span class="c1"&gt;# 4. Cache check (same content AND same intent gets the same answer)
&lt;/span&gt;&lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_cached_compressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;p&gt;Three decisions in this patch are worth explaining, because the obvious fix is wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Don't change &lt;code&gt;sid&lt;/code&gt; — add a key beside it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The tempting one-liner is to fold the query into &lt;code&gt;sid&lt;/code&gt; and move on. That breaks something&lt;br&gt;
else: &lt;code&gt;expand_context&lt;/code&gt; resolves a compressed reference back to its original &lt;em&gt;by content&lt;br&gt;
hash&lt;/em&gt;. &lt;code&gt;sid&lt;/code&gt; has to stay content-only for that to keep working. So the patch introduces a&lt;br&gt;
separate &lt;code&gt;cache_key&lt;/code&gt; and leaves &lt;code&gt;sid&lt;/code&gt; exactly as it was. Two identifiers, two jobs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Classify &lt;code&gt;kind&lt;/code&gt; before building the key, not after.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kind&lt;/code&gt; was being resolved &lt;em&gt;after&lt;/em&gt; the cache check. That meant a caller passing &lt;code&gt;kind=None&lt;/code&gt;&lt;br&gt;
and a caller passing the same kind explicitly would key differently, even though the&lt;br&gt;
compressor would treat them identically — a cache miss on every first call, forever.&lt;/p&gt;

&lt;p&gt;Moving classification above the cache check fixed that, and it fixed a second bug on the&lt;br&gt;
way: only &lt;code&gt;LocalModelStrategy&lt;/code&gt; sniffed &lt;code&gt;kind&lt;/code&gt; internally, so &lt;code&gt;GpuServerStrategy&lt;/code&gt; was&lt;br&gt;
forwarding &lt;code&gt;kind=None&lt;/code&gt; to the server. Central classification means every backend now&lt;br&gt;
receives a real value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Join the parts with a null byte.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"\x00".join(...)&lt;/code&gt; rather than &lt;code&gt;"-".join(...)&lt;/code&gt; or plain concatenation. Without a separator&lt;br&gt;
that cannot occur in the inputs, &lt;code&gt;level="L0"&lt;/code&gt; + &lt;code&gt;kind="1code"&lt;/code&gt; and &lt;code&gt;level="L01"&lt;/code&gt; +&lt;br&gt;
&lt;code&gt;kind="code"&lt;/code&gt; collide into the same key. It costs nothing and removes a class of bug that&lt;br&gt;
would be genuinely miserable to track down later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The test.&lt;/strong&gt; &lt;code&gt;tests/test_cache_key_intent.py&lt;/code&gt; (+92) asserts the property directly: same&lt;br&gt;
content with different queries must not return the same object, and the same content with&lt;br&gt;
the same intent must hit the cache. It fails on the original code and passes on the patch,&lt;br&gt;
which is the only claim about a fix I actually trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took away from it
&lt;/h2&gt;

&lt;p&gt;I found this because my harness printed &lt;code&gt;cache_hit&lt;/code&gt; next to every row. If it had only&lt;br&gt;
printed token counts, two identical numbers would have looked like a compressor being&lt;br&gt;
consistent, which is exactly what you want it to be.&lt;/p&gt;

&lt;p&gt;The bug was invisible in the metric everyone watches and obvious in the one nobody logs.&lt;br&gt;
Since then I print the cache flag next to every cached result I benchmark — the cost is one&lt;br&gt;
column, and it is the column that told me the answer I was measuring wasn't mine.&lt;/p&gt;

</description>
      <category>bugsmash</category>
      <category>devchallenge</category>
      <category>debugging</category>
      <category>python</category>
    </item>
  </channel>
</rss>
