<?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: Michael Kaminski</title>
    <description>The latest articles on DEV Community by Michael Kaminski (@makaminski1337).</description>
    <link>https://dev.to/makaminski1337</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%2F1224430%2Fe62f2803-0300-4d09-bf54-cf77c63b5779.png</url>
      <title>DEV Community: Michael Kaminski</title>
      <link>https://dev.to/makaminski1337</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/makaminski1337"/>
    <language>en</language>
    <item>
      <title>One Knowledge Base, Four Surfaces: Pages, Graph, Search Index, and MCP</title>
      <dc:creator>Michael Kaminski</dc:creator>
      <pubDate>Sun, 23 Aug 2026 18:37:28 +0000</pubDate>
      <link>https://dev.to/makaminski1337/one-knowledge-base-four-surfaces-pages-graph-search-index-and-mcp-5a5p</link>
      <guid>https://dev.to/makaminski1337/one-knowledge-base-four-surfaces-pages-graph-search-index-and-mcp-5a5p</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.michael-kaminski.io/writing/one-knowledge-base-four-surfaces" rel="noopener noreferrer"&gt;michael-kaminski.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Genome of Games publishes the same 1,180 records four different ways, and one command writes all four: &lt;code&gt;node build.js&lt;/code&gt;, 0.39 seconds, zero npm dependencies.&lt;/p&gt;

&lt;p&gt;Out come 1,245 static HTML pages for crawlers, an interactive canvas graph for humans, a 129,037-byte search index for the site's own search box, and a Model Context Protocol server exposing 8 tools to agents.&lt;/p&gt;

&lt;p&gt;The decision worth copying is the one that sounds like a downgrade. The MCP server does not query the site and does not read the source data. It statically imports a 1.9 MB index that the build wrote. There is exactly one place where slugs, lineage, and adoption edges get joined, so an agent and a crawler cannot come back with different answers.&lt;/p&gt;

&lt;p&gt;The dataset is an ontology of video game mechanics — 168 mechanics, 618 games, 394 companies, 4,366 recorded links, 1962 to 2025. What the records are about does not matter here. The shape of the problem shows up anywhere a structured knowledge base has to serve both a search engine and a model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four surfaces, one build, a twelve-fold expansion
&lt;/h2&gt;

&lt;p&gt;Six hand-edited JSON files under &lt;code&gt;data/&lt;/code&gt; are the source of truth: the feature ontology, the graph, the prose, the company registry, the site copy, and the verified outbound links. Together they are 1,312,577 bytes.&lt;/p&gt;

&lt;p&gt;The build turns that into 16,644,215 bytes of generated read surface. A 12.7× expansion, and every byte of it is disposable.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Surface&lt;/th&gt;
&lt;th&gt;Consumer&lt;/th&gt;
&lt;th&gt;Bytes&lt;/th&gt;
&lt;th&gt;Per entity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,245 static HTML pages&lt;/td&gt;
&lt;td&gt;Crawlers, humans&lt;/td&gt;
&lt;td&gt;14,613,203&lt;/td&gt;
&lt;td&gt;11,728 / page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mcp-index.json&lt;/code&gt; → MCP server&lt;/td&gt;
&lt;td&gt;Agents&lt;/td&gt;
&lt;td&gt;1,901,975&lt;/td&gt;
&lt;td&gt;1,612&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;search-index.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The site's own search box&lt;/td&gt;
&lt;td&gt;129,037&lt;/td&gt;
&lt;td&gt;109&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/graph/&lt;/code&gt; canvas&lt;/td&gt;
&lt;td&gt;Humans exploring lineage&lt;/td&gt;
&lt;td&gt;data injected at build&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The build also emits &lt;code&gt;sitemap.xml&lt;/code&gt; with 1,245 entries, &lt;code&gt;llms.txt&lt;/code&gt;, &lt;code&gt;robots.txt&lt;/code&gt;, and a 404 page. The same run reports 96,843 internal links across those pages.&lt;/p&gt;

&lt;p&gt;Nothing in that list is authored. Delete the whole output directory and the next build restores it in under half a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent surface is a build artifact, not a query path
&lt;/h2&gt;

&lt;p&gt;The obvious way to serve an agent is to put an API in front of the data and let the MCP server call it. That is the version that rots.&lt;/p&gt;

&lt;p&gt;An API layer has to re-derive the same things the page renderer derives — how a name becomes a slug, which parents count as ancestors, which adoption edges are shown. Two implementations of one join is two implementations that will disagree, and the disagreement surfaces as an agent confidently citing a URL that renders something else.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;build.js&lt;/code&gt; writes &lt;code&gt;data/mcp-index.json&lt;/code&gt; as a build step, and &lt;code&gt;api/mcp.mjs&lt;/code&gt; opens with a static import of it. The serverless function holds no derivation logic at all. Its first line of real work is &lt;code&gt;const { meta, families, eras, entities } = INDEX&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The protocol itself is spoken by hand — JSON-RPC over POST, no MCP SDK, for the same reason there is no Stripe or Supabase SDK anywhere in the repo. &lt;code&gt;initialize&lt;/code&gt;, &lt;code&gt;tools/list&lt;/code&gt;, &lt;code&gt;tools/call&lt;/code&gt;, &lt;code&gt;ping&lt;/code&gt;, and two notifications is the entire surface area. The whole server is 21,508 bytes.&lt;/p&gt;

&lt;p&gt;You can check it from a terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://genome-of-games.vercel.app/api/mcp/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":1,"method":"tools/list"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eight tools come back: overview, search, get_mechanic, get_game, get_studio, trace_lineage, list_family, by_year.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent pays 14.7× the search index, per entity, and should
&lt;/h2&gt;

&lt;p&gt;Same 1,180 entities, two indexes, wildly different budgets. The search index spends 109 bytes per entity. The MCP index spends 1,612. That ratio is 14.7×, and it is the most useful number in the build.&lt;/p&gt;

&lt;p&gt;The search box only needs enough to rank a substring match and hand over a URL: name, type, year, path. Everything else is one navigation away, and the human doing the navigating is the retrieval system.&lt;/p&gt;

&lt;p&gt;An agent has no second hop it can afford. If &lt;code&gt;genome_get_mechanic&lt;/code&gt; returns a stub, the model either guesses or makes four more tool calls, and both outcomes are worse than a fat payload. So the MCP record carries the credited origin game and its developer, the full prose essay, parents, children, everything downstream, and the later adopters — pre-joined.&lt;/p&gt;

&lt;p&gt;The generic version of this: size a machine-readable surface by how many round trips the consumer can tolerate, not by what looks tidy. Humans tolerate many. Agents tolerate roughly one.&lt;/p&gt;

&lt;p&gt;The same logic drives the tool descriptions. &lt;code&gt;genome_get_overview&lt;/code&gt; spends its budget telling the model that "origin" means the first notable &lt;em&gt;shipped&lt;/em&gt; implementation rather than invention, that over-the-shoulder aim is credited to &lt;em&gt;kill.switch&lt;/em&gt; in 2003 rather than &lt;em&gt;Resident Evil 4&lt;/em&gt; in 2005, and that 949 of 1,180 entities carry a verified Wikipedia permalink while the remaining 231 carry none. Coverage gaps are a tool output, not a footnote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Determinism is what makes any of this checkable
&lt;/h2&gt;

&lt;p&gt;Because &lt;code&gt;mcp-index.json&lt;/code&gt; is committed rather than gitignored, a rebuild is a falsifiable claim. I ran the build three times: 0.40s, 0.39s, 0.39s. The index md5 was &lt;code&gt;ba9928a35651fb201b4e0c3e0cab61d7&lt;/code&gt; before and after, and &lt;code&gt;git status&lt;/code&gt; reported zero changed files.&lt;/p&gt;

&lt;p&gt;That is the whole verification story. If a build ever produces a diff on a run where the source did not change, the pipeline has picked up a clock, a hash seed, or a network call, and the "one join" guarantee is already gone.&lt;/p&gt;

&lt;p&gt;Committing generated output is usually bad practice. Here it buys a cheap integrity test on every pull request, and it keeps the build offline and reproducible — the Wikipedia verification pass runs separately, by hand, and writes its results into the committed data.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost: the surface the build does not own has already drifted
&lt;/h2&gt;

&lt;p&gt;Single source of truth holds only for surfaces inside the build step. The README is outside it, and it is already wrong.&lt;/p&gt;

&lt;p&gt;The README says 1,243 pages and 89,351 internal links. The build says 1,245 and 96,843. Off by two pages, and off by 7,492 links — an 8.4% understatement, sitting in the first file anyone reads.&lt;/p&gt;

&lt;p&gt;Nobody edited a number badly. Content was added, every generated surface absorbed it silently, and the one hand-written surface stayed where it was. That is the failure mode this architecture produces: it does not create disagreement between surfaces it owns, and it hides disagreement with surfaces it does not.&lt;/p&gt;

&lt;p&gt;The fix is not discipline. It is either generating the README's numbers too, or asserting them in the build and failing loudly. I have done neither yet, which is why this paragraph exists.&lt;/p&gt;

&lt;p&gt;There is a second bill. The MCP function statically imports 1.9 MB, so every cold start pays for the whole dataset whether the caller wanted one mechanic or the overview. At this size that is a fine trade. At 20 MB it would not be, and the answer then is a real index with range reads — which reintroduces exactly the derivation layer this design removed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would copy, and what I would not
&lt;/h2&gt;

&lt;p&gt;Copy the direction of the dependency. Generated artifact in, no query out. The agent surface should be downstream of the same build that produces the pages, never a sibling of it.&lt;/p&gt;

&lt;p&gt;Copy the honesty budget in the tool descriptions. A model that is told where a dataset is thin cites it more carefully than one that is handed clean-looking records.&lt;/p&gt;

&lt;p&gt;Do not copy the static import past a few megabytes, and do not copy hand-rolled JSON-RPC into a codebase that already has dependencies — the only reason it is defensible here is that the alternative was the repo's first one.&lt;/p&gt;

&lt;p&gt;If you are shipping a knowledge base to both crawlers and agents, I want to know what your per-entity byte ratio is between the two surfaces. Mine is 14.7×. I have not seen anyone else publish theirs, and it is the number that decides whether the agent has to make a second call.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>architecture</category>
      <category>seo</category>
    </item>
    <item>
      <title>Shipping an AI Agent Through Compliance Review in Regulated Lending</title>
      <dc:creator>Michael Kaminski</dc:creator>
      <pubDate>Mon, 17 Aug 2026 02:09:32 +0000</pubDate>
      <link>https://dev.to/makaminski1337/shipping-an-ai-agent-through-compliance-review-in-regulated-lending-emh</link>
      <guid>https://dev.to/makaminski1337/shipping-an-ai-agent-through-compliance-review-in-regulated-lending-emh</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.michael-kaminski.io/writing/shipping-an-ai-agent-through-compliance-review" rel="noopener noreferrer"&gt;michael-kaminski.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On May 12, 2025 the CFPB withdrew 67 guidance documents in a single Federal Register notice: 8 policy statements, 7 interpretive rules, 13 advisory opinions, and 39 other guidance documents. Two of them were the circulars that told lenders how adverse-action notices had to work when a complex algorithm made the decision.&lt;/p&gt;

&lt;p&gt;Not one word of the underlying duty changed. The Equal Credit Opportunity Act still requires a statement of specific reasons, and Regulation B still says what "specific" means.&lt;/p&gt;

&lt;p&gt;So the rule I now build against: design the agent against the statute and against the evidence it will have to produce. Guidance is the most volatile layer in the stack and the easiest one to mistake for the requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guidance is the layer that moves. Write against the layer that doesn't.
&lt;/h2&gt;

&lt;p&gt;There are three layers, and they have very different half-lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Statute&lt;/strong&gt; is Congress: ECOA, the FDCPA, GLBA. &lt;strong&gt;Regulation&lt;/strong&gt; is notice-and-comment rulemaking: 12 CFR 1002 (Regulation B), 12 CFR 1006 (Regulation F). &lt;strong&gt;Guidance&lt;/strong&gt; is circulars, bulletins, and advisory opinions, which is to say a memo.&lt;/p&gt;

&lt;p&gt;On one day in May 2025, the third layer moved 67 times. The second and first layers did not move at all.&lt;/p&gt;

&lt;p&gt;It keeps moving, too, and not only in bulk. The Regulation B advisory opinion on Special Purpose Credit Programs, on the books since January 2021, was withdrawn on June 17, 2026 — more than a year after the mass withdrawal, on its own.&lt;/p&gt;

&lt;p&gt;If a control in your design document cites a circular, that control has the shelf life of a memo. Cite the section of the regulation instead. It is a slower read and it survives administrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The review is an evidence problem, not a model problem
&lt;/h2&gt;

&lt;p&gt;I prepared for my first one as though it were about model behavior — accuracy, refusal rates, how the thing handles an ugly prompt. Reviewers asked about records.&lt;/p&gt;

&lt;p&gt;Nearly every question reduced to the same four: what did the system decide, on what inputs, under which version of the instructions and the model, and can you reproduce that answer later when someone asks.&lt;/p&gt;

&lt;p&gt;"Later" is the word carrying the weight. It is not a design detail. It has a number, and the number is bigger than most teams assume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your trace retention floor is 120 days, not 30
&lt;/h2&gt;

&lt;p&gt;Regulation B, 12 CFR 1002.9, sets a chain of clocks on a credit application.&lt;/p&gt;

&lt;p&gt;The creditor has &lt;strong&gt;30 days&lt;/strong&gt; after a completed application to notify the applicant of the action taken. That notice either carries the specific reasons, or it discloses the applicant's right to request them. If it discloses the right, the applicant has &lt;strong&gt;60 days&lt;/strong&gt; from the notification to ask, and the creditor then has &lt;strong&gt;30 days&lt;/strong&gt; to deliver.&lt;/p&gt;

&lt;p&gt;30 + 60 + 30 = &lt;strong&gt;120 days&lt;/strong&gt;. That is the worst-case gap between the moment the agent produces a decision and the moment someone has to state, precisely, why it made that decision.&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%2Fk4yp728qg7vr4o468jf7.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%2Fk4yp728qg7vr4o468jf7.png" alt="The 30 + 60 + 30 day clock chain in 12 CFR 1002.9, against what a 30-day log covers" width="800" height="374"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Three clocks, one window. A 30-day logging default covers the first one and none of the other two.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Thirty days is a common default retention on logging platforms, and defaults are how most retention windows get set. Inherit that one and the decision stays reproducible for a quarter of the window in which it can be demanded.&lt;/p&gt;

&lt;p&gt;That gap is not a nit. It is the difference between answering an examiner and telling an examiner you cannot answer.&lt;/p&gt;

&lt;p&gt;And 120 days is the floor from the notification path alone. Records-retention rules run longer, and the practical constraint is usually the exam cycle rather than any of these clocks. Size the trace to the longest one, not the nearest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reason string has to come from the thing that made the decision
&lt;/h2&gt;

&lt;p&gt;Regulation B requires that the statement of reasons be specific and indicate the principal reasons for the action. It goes further and names two answers that do not count: that the applicant failed to achieve a qualifying score, and that the decision rested on the creditor's internal standards or policies.&lt;/p&gt;

&lt;p&gt;That rules out the most convenient agent architecture available to you.&lt;/p&gt;

&lt;p&gt;The convenient one is two models: the first produces a decision, the second reads the decision and writes an explanation. It is easy to build, it demos beautifully, and the output is fluent.&lt;/p&gt;

&lt;p&gt;It is also a caption, not a reason. The second model is describing the first model's output, not its cause. If the decision was driven by features the explanation layer never saw, the explanation is a plausible guess that happens to be well-written — which is the worst possible failure mode, because nothing about it looks wrong.&lt;/p&gt;

&lt;p&gt;The reason has to be emitted by the decision path itself, as a byproduct of the decision, in the same transaction. If that is expensive, the expense is the requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Counting is a compliance control, and counting is where the bug will be
&lt;/h2&gt;

&lt;p&gt;Regulation F, 12 CFR 1006.14, in effect since November 30, 2021, presumes a violation if a collector places more than 7 calls about a particular debt within 7 consecutive days, or calls within 7 days of a live conversation about that debt.&lt;/p&gt;

&lt;p&gt;The window is rolling. Here is what it costs to implement it as a calendar week.&lt;/p&gt;

&lt;p&gt;A counter that resets Monday at 00:00 will happily allow 7 calls placed Saturday and Sunday, then 7 more on Monday and Tuesday. That is 14 calls inside a four-day span. The counter never reads above 7 in either week, and the presumption fires anyway.&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%2Frzbn6y9018adb3ufptgl.gif" 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%2Frzbn6y9018adb3ufptgl.gif" alt="Animated: a calendar-week counter and a rolling 7-day counter running against the same 14 calls" width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Same 14 calls, four days, two counters. The calendar counter resets Monday and never reads above 7. The rolling counter — the one the regulation describes — passes the limit on call 8.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Rolling window, rolling counter. Keyed per consumer and per debt, because the limit is per debt and a consumer with two accounts has two independent budgets.&lt;/p&gt;

&lt;p&gt;There is a second version of the same bug that only appears in multi-agent designs. Give the voice agent and the messaging agent one counter each and they will sum to twice the limit while both report compliance. The counter belongs to the account, not to the agent that happens to be calling.&lt;/p&gt;

&lt;p&gt;None of that needs a model. It needs a row lock. The general lesson from the review was that the parts of the system a regulator cares most about are usually the parts that should never have been probabilistic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Effective challenge means an adversary with standing
&lt;/h2&gt;

&lt;p&gt;Banks have been running a model governance framework since well before any of this: SR 11-7, issued jointly by the Federal Reserve and the OCC in 2011. Its center of gravity is a phrase worth stealing regardless of your industry — &lt;strong&gt;effective challenge&lt;/strong&gt;. Critical review by informed, technically competent people who are independent of the team that built the model.&lt;/p&gt;

&lt;p&gt;Independence is the half everyone implements. Standing is the half that decides whether the review was real.&lt;/p&gt;

&lt;p&gt;A reviewer who can file a concern but cannot stop a launch is not effective challenge. That is a comment. The test is simple and slightly uncomfortable: name the person who can say no, confirm they know they can, and do it before you build rather than the week you want to ship.&lt;/p&gt;

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

&lt;p&gt;Time, and a specific kind of scope loss that is worth naming honestly.&lt;/p&gt;

&lt;p&gt;Everything above pushes the design toward the boring architecture. Deterministic code where the rule is deterministic. A model only where judgment genuinely helps. The frequency counter becomes a database constraint. The reason string becomes a required output of the decision path. The model is left with the part that is actually hard.&lt;/p&gt;

&lt;p&gt;That is a visibly smaller agent than the prototype. The prototype ran the whole workflow end to end and was, frankly, more impressive in a demo. The reviewable version hands three of those steps back to a state machine and looks less like the future.&lt;/p&gt;

&lt;p&gt;I would ship the smaller one every time. In a regulated line the thing that kills a program is rarely a slow launch. It is an unreproducible decision surfacing in an examination eighteen months later, when the person who built the system has moved teams and the log rolled off at day 30.&lt;/p&gt;

&lt;p&gt;If you have taken an agent through a compliance review in lending, I want to know one number: where did your trace retention actually land, and was it chosen or inherited from a logging default? That is the number I would check first in anyone's design, including my own.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>fintech</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Statistical Gating for Agent Instruction Changes</title>
      <dc:creator>Michael Kaminski</dc:creator>
      <pubDate>Sun, 09 Aug 2026 19:16:23 +0000</pubDate>
      <link>https://dev.to/makaminski1337/statistical-gating-for-agent-instruction-changes-2ln8</link>
      <guid>https://dev.to/makaminski1337/statistical-gating-for-agent-instruction-changes-2ln8</guid>
      <description>&lt;p&gt;An edit to an agent's instruction file is a deploy, and I stopped letting mine ship on a hunch.&lt;br&gt;
The daily tuning job that maintains my assistant's &lt;code&gt;CLAUDE.md&lt;/code&gt; now requires a statistically&lt;br&gt;
significant improvement — Welch's t-test, p &amp;lt; 0.10, at least a 5% lift — measured against a&lt;br&gt;
14-day rolling baseline, before an instruction change is allowed to stay.&lt;/p&gt;

&lt;p&gt;Then I ran the power calculation on my own gate and found the 5% threshold is decorative. At&lt;br&gt;
14 days per window the test can only detect a shift of roughly &lt;strong&gt;0.97 standard deviations&lt;/strong&gt;.&lt;br&gt;
Anything smaller is invisible regardless of what the lift threshold claims.&lt;/p&gt;

&lt;p&gt;Both halves of that are this post. The mechanism is worth copying. The window size is the part&lt;br&gt;
I got wrong, and the arithmetic is short enough to check.&lt;/p&gt;

&lt;h2&gt;
  
  
  The prompt is the least-tested code in most agent systems
&lt;/h2&gt;

&lt;p&gt;Every other artifact in an agent pipeline has a gate. Application code gets a test suite and a&lt;br&gt;
review. Infrastructure gets a plan and a diff — I have written enough Terraform modules and&lt;br&gt;
managed enough remote state to know nobody merges those blind.&lt;/p&gt;

&lt;p&gt;The instruction file gets none of that. Someone notices the agent did something annoying, adds&lt;br&gt;
a line telling it not to, and ships. There is no baseline, no holdout, and no record of whether&lt;br&gt;
the previous eleven lines are still earning their tokens.&lt;/p&gt;

&lt;p&gt;That is how instruction files rot. They accumulate rules that were true about one bad afternoon&lt;br&gt;
and have been costing context ever since.&lt;/p&gt;

&lt;h2&gt;
  
  
  You cannot test what you do not score
&lt;/h2&gt;

&lt;p&gt;The gate needs a dependent variable, so the first build was a rubric, not a test. Five&lt;br&gt;
dimensions, each scored 0–10 per conversation: goal clarity, rework rate, context hit rate,&lt;br&gt;
scope discipline, and response density. The daily score is the average across that day's&lt;br&gt;
conversations, weighted by conversation length.&lt;/p&gt;

&lt;p&gt;The rubric is frozen. Changing it invalidates every historical comparison, so a rubric change is&lt;br&gt;
itself a tracked meta-edit that resets all baselines.&lt;/p&gt;

&lt;p&gt;Scores are anchored to observable friction rather than self-assessment. The scorer parses&lt;br&gt;
transcripts for five pattern classes — corrections ("no, that", "actually", "don't"), rework&lt;br&gt;
("redo", "start over"), scope drift ("I didn't ask", "just do"), missed context ("I told you&lt;br&gt;
before", "check memory"), and praise ("exactly", "nailed it"). Friction pulls the dimension&lt;br&gt;
score down, praise pulls it up.&lt;/p&gt;

&lt;p&gt;This is the load-bearing decision. An agent grading its own transcripts will drift toward&lt;br&gt;
generosity. Regex over the human's actual words will not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Welch, not Student, and why that is not pedantry
&lt;/h2&gt;

&lt;p&gt;Each edit gets a 14-day pre-window and a 14-day post-window of daily scores, compared with&lt;br&gt;
Welch's t-test. Three outcomes: p &amp;lt; 0.10 and lift ≥ 5% marks the edit &lt;strong&gt;KEPT&lt;/strong&gt;; p &amp;lt; 0.10 and&lt;br&gt;
lift ≤ −5% triggers an &lt;strong&gt;automatic revert&lt;/strong&gt;; everything else is &lt;strong&gt;INCONCLUSIVE&lt;/strong&gt; and the edit&lt;br&gt;
stays on probation.&lt;/p&gt;

&lt;p&gt;Welch rather than Student's t because the two windows should not have equal variance. A good&lt;br&gt;
instruction usually works by removing a failure mode, which compresses the bad tail — the&lt;br&gt;
variance drops as much as the mean rises.&lt;/p&gt;

&lt;p&gt;Student's t assumes equal variance and over-rejects when the smaller-variance group is the&lt;br&gt;
larger sample. That is precisely the case you care about, so the pooled test would hand you your&lt;br&gt;
most confident false positives on your best edits. Welch costs a few degrees of freedom and&lt;br&gt;
removes the assumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  The arithmetic that broke the design
&lt;/h2&gt;

&lt;p&gt;A two-sample test at α = 0.10 and 80% power detects a minimum effect of roughly&lt;br&gt;
(t_α/2 + t_β) × √(2/n) standard deviations. For a 14-day window that is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Days per window&lt;/th&gt;
&lt;th&gt;Minimum detectable effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;1.42 SD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;0.97 SD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;0.78 SD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;0.67 SD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;0.55 SD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;0.46 SD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2Ftmlaotx498raiq3kh42h.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%2Ftmlaotx498raiq3kh42h.png" alt="Minimum detectable effect by window length" width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The gate can only see effects above the yellow line. The 5% threshold sits inside the shaded band — under it the whole time, doing no work.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now convert to the units the gate actually uses. Assume a 7.0 baseline on the 0–10 rubric and a&lt;br&gt;
day-to-day standard deviation of 0.6 points, which is unremarkable for a metric averaged over a&lt;br&gt;
handful of conversations.&lt;/p&gt;

&lt;p&gt;A 5% lift is 0.35 points, or 0.58 SD. The 14-day window detects 0.97 SD, which is 0.58 points —&lt;br&gt;
&lt;strong&gt;8.3% of baseline&lt;/strong&gt;. The statistical test is roughly 1.7× stricter than the lift threshold&lt;br&gt;
sitting next to it.&lt;/p&gt;

&lt;p&gt;So the 5% number never binds. It is doing no work. Every edit that clears the t-test has already&lt;br&gt;
cleared 5% by a wide margin, and every edit that fails does so on power, not on effect size.&lt;/p&gt;

&lt;p&gt;Detecting a genuine 5% lift at that variance takes &lt;strong&gt;38 days per window&lt;/strong&gt;, not 14. And the&lt;br&gt;
requirement moves fast with variance:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Daily score SD&lt;/th&gt;
&lt;th&gt;5% lift, in SD&lt;/th&gt;
&lt;th&gt;Days/window needed&lt;/th&gt;
&lt;th&gt;What 14 days actually detects&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.4&lt;/td&gt;
&lt;td&gt;0.88&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;5.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;6.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.6&lt;/td&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;8.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.8&lt;/td&gt;
&lt;td&gt;0.44&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;td&gt;11.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.0&lt;/td&gt;
&lt;td&gt;0.35&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;13.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2F0kwsdh1dxek33aacnf7i.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%2F0kwsdh1dxek33aacnf7i.png" alt="What a 5% threshold actually means, by variance" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The honest label on the gate. Every bar right of the dashed line is a threshold the config never mentions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Read the last column as the honest label on the gate. At SD 1.0 a "5% threshold" is really a 14%&lt;br&gt;
threshold, and the difference is entirely hidden from whoever reads the config file.&lt;/p&gt;

&lt;p&gt;The window has to be sized to the variance, not to a calendar. Fourteen days was chosen because&lt;br&gt;
two weeks is a tidy number, which is not a reason.&lt;/p&gt;

&lt;p&gt;There are two ways out and only one of them is cheap. Lengthen the window, and you wait longer&lt;br&gt;
per lesson. Or shrink the variance by scoring more conversations per day — the daily mean's&lt;br&gt;
standard error falls with √n, so tripling daily volume cuts the SD by about 42% and pulls the&lt;br&gt;
required window from 38 days down to roughly 17.&lt;/p&gt;

&lt;p&gt;Put the same arithmetic the other way around. If an edit truly delivers a 5% lift, a 14-day&lt;br&gt;
window calls it significant &lt;strong&gt;44% of the time&lt;/strong&gt;. That is a coin flip on your own best changes.&lt;br&gt;
At 38 days it is 81%.&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%2F7f7ln1g6bu80pbx62bcd.gif" 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%2F7f7ln1g6bu80pbx62bcd.gif" alt="Animated: power to detect a real 5% lift, by window length" width="800" height="452"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Same arithmetic, run forward. Each frame is a window length; the shaded tail is how often a&lt;br&gt;
genuinely good edit gets called significant.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the gate cannot do, stated plainly
&lt;/h2&gt;

&lt;p&gt;It does not control for multiple comparisons. At the configured cadence of one low-risk edit&lt;br&gt;
every three days, that is about 122 evaluations a year. At α = 0.10 two-sided, noise alone&lt;br&gt;
produces roughly &lt;strong&gt;six spurious KEPT verdicts and six spurious auto-reverts per year&lt;/strong&gt;. The&lt;br&gt;
auto-revert side is the one that stings: the system will occasionally roll back a good edit with&lt;br&gt;
statistical confidence.&lt;/p&gt;

&lt;p&gt;It is also not a randomized experiment. Pre and post windows are consecutive calendar time, so a&lt;br&gt;
model version change, a vacation, or a month of unusually messy work lands entirely in one window&lt;br&gt;
and gets attributed to the edit.&lt;/p&gt;

&lt;p&gt;That makes this a noise filter, not a causal claim. It stops the obviously-worse edits and the&lt;br&gt;
obviously-imaginary wins. It will not tell you why anything moved.&lt;/p&gt;

&lt;h2&gt;
  
  
  The safeguards do more work than the test
&lt;/h2&gt;

&lt;p&gt;Three rules keep the loop from eating itself, and they matter more than the p-value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cooldowns.&lt;/strong&gt; One low-risk auto-edit per three days, one high-risk proposal per week. Without a&lt;br&gt;
cooldown the windows overlap so badly that no edit is ever cleanly attributable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plateau detection.&lt;/strong&gt; If the 14-day rolling score has not improved 2% over the prior 14 days and&lt;br&gt;
no friction is firing, the day's edit is skipped. The easy wins arrive early; after that, editing&lt;br&gt;
is mostly a way to add variance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A risk split with a human in it.&lt;/strong&gt; Wording and formatting changes auto-apply. Anything that&lt;br&gt;
adds a section or changes how the agent decides gets written to a proposals folder and waits.&lt;br&gt;
Every edit backs up the prior file first and lands in a revertible log, tagged, so a bad call is&lt;br&gt;
a one-line rollback rather than an archaeology project.&lt;/p&gt;

&lt;p&gt;The config file has a section titled "when NOT to edit," and the last line of it is the most&lt;br&gt;
useful thing in the whole system: &lt;em&gt;doing nothing is always a valid action.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost
&lt;/h2&gt;

&lt;p&gt;Latency. That is the whole bill, and it is larger than I estimated when I built this.&lt;/p&gt;

&lt;p&gt;A gated instruction file learns on a 14-day clock at best and, if the variance numbers above are&lt;br&gt;
right, a 38-day clock in practice. An ungated one learns in an afternoon and is wrong in ways&lt;br&gt;
nobody measures. I would still take the slow version, but I would not pretend the tradeoff is&lt;br&gt;
free, and I would not build this at all for a system I was still prototyping.&lt;/p&gt;

&lt;p&gt;I have no KEPT verdicts to report yet, because the first honest one cannot exist until a full&lt;br&gt;
post-window closes. Publishing the design before the results is the point — the design is the&lt;br&gt;
part that is checkable, and the arithmetic above is the part I would want someone to argue with.&lt;/p&gt;

&lt;p&gt;If you are gating prompt or instruction changes statistically at your shop, I want to know what&lt;br&gt;
window you landed on and what your daily variance looks like. That number is the whole ballgame&lt;br&gt;
and almost nobody publishes it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://www.michael-kaminski.io/writing/statistical-gating-for-agent-instruction-changes" rel="noopener noreferrer"&gt;michael-kaminski.io&lt;/a&gt;. I write field notes on agent infrastructure — evals, MCP servers, and what it costs to run agents in production.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>statistics</category>
      <category>devops</category>
    </item>
    <item>
      <title>🚀 Boosting Our Project with Strategic Unit Testing: A Closer Look at Our Latest Code 🛠️</title>
      <dc:creator>Michael Kaminski</dc:creator>
      <pubDate>Sun, 14 Jan 2024 19:20:58 +0000</pubDate>
      <link>https://dev.to/makaminski1337/boosting-our-project-with-strategic-unit-testing-a-closer-look-at-our-latest-code-5c66</link>
      <guid>https://dev.to/makaminski1337/boosting-our-project-with-strategic-unit-testing-a-closer-look-at-our-latest-code-5c66</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello Devs! 👋 We've just rolled out an exciting update in our project, integrating a series of unit tests that are set to elevate our application's reliability and maintainability to new heights. Let’s dive deep into this new code block and explore its impact on our SDLC. Check out our &lt;a href="https://github.com/MAKaminski/IconGenerator"&gt;repo&lt;/a&gt; here for the full codebase!&lt;/p&gt;

&lt;p&gt;The New Code Block&lt;br&gt;
Here's a snippet from our latest addition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import unittest
from unittest.mock import patch, MagicMock, Mock
from PIL import Image
from io import BytesIO
import main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This segment sets the stage for our unit testing, importing essential modules and our main module where our business logic resides.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tests 🔍
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Testing User Input Prompt
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@patch('main.input', return_value='test')
def test_prompt_user_for_theme(self, input):
    self.assertEqual(main.prompt_user_for_theme(), 'test')

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 This test ensures that our prompt_user_for_theme function correctly captures and returns user input.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetching Images from Unsplash
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def test_fetch_images_from_unsplash(self):
    # Mock setup...
    with patch('main.requests.get', return_value=mock_response) as mock_get:
        images = main.fetch_images_from_unsplash('test', {'match_aspect_ratio': False})
        self.assertEqual(len(images), 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 By mocking a response, this test verifies if the correct number of images is fetched from Unsplash.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Image Conversion to Grayscale
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def test_convert_to_grayscale_and_contrast(self):
    img = Image.new('RGB', (64, 64))
    converted_img = main.convert_to_grayscale_and_contrast(img)
    self.assertEqual(converted_img.mode, 'L')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 This snippet checks if our image conversion functionality is up to the mark.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Saving Icons to Directory
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@patch('main.os.path.exists', return_value=False)
@patch('main.os.makedirs')
@patch('PIL.Image.new')
def test_save_icons_to_directory(self, mock_new, mock_makedirs, mock_exists):
    # Test implementation...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 It tests the creation of directories and saving of icons, ensuring our file system interactions are flawless.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Value of These Tests 💡
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reliability Boost:&lt;/strong&gt; Ensures each component works correctly in isolation.&lt;br&gt;
&lt;strong&gt;Speedy Debugging:&lt;/strong&gt; Pinpoints issues, cutting down on troubleshooting time.&lt;br&gt;
&lt;strong&gt;Encourages Better Design:&lt;/strong&gt; Leads to a more modular and maintainable codebase.&lt;br&gt;
&lt;strong&gt;CI Integration:&lt;/strong&gt; Ready for automated testing in CI pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impact on SDLC 🔄
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Planning &amp;amp; Analysis&lt;/strong&gt;&lt;br&gt;
1.: Our tests provide clarity on functionality, aiding in precise planning.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Design:&lt;/strong&gt; They encourage a design that's modular and easy to test.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Implementation:&lt;/strong&gt; Immediate feedback on code changes reduces bugs.&lt;br&gt;
&lt;strong&gt;Testing &amp;amp; Integration:&lt;/strong&gt; Continuous testing elevates quality and integration.&lt;br&gt;
&lt;strong&gt;Maintenance:&lt;/strong&gt; They make updates and extensions safer and easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;🌟 The addition of these unit tests marks a significant leap in our quest for a robust and maintainable application. By ensuring each component's functionality, we not only prevent bugs but foster a development culture centered around quality and efficiency.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Explore More:&lt;/strong&gt; Dive into our &lt;a href="https://github.com/MAKaminski/IconGenerator"&gt;repository&lt;/a&gt; here for the full code and more insights!&lt;/p&gt;

&lt;p&gt;📢 Stay tuned for more updates and let's code towards excellence together! 🚀💻&lt;/p&gt;

</description>
      <category>python</category>
      <category>unittest</category>
      <category>sdlc</category>
    </item>
    <item>
      <title>Introducing Unsplash Image Fetcher: A Practical Approach to Image Processing</title>
      <dc:creator>Michael Kaminski</dc:creator>
      <pubDate>Sun, 14 Jan 2024 18:48:28 +0000</pubDate>
      <link>https://dev.to/makaminski1337/introducing-unsplash-image-fetcher-a-practical-approach-to-image-processing-29eh</link>
      <guid>https://dev.to/makaminski1337/introducing-unsplash-image-fetcher-a-practical-approach-to-image-processing-29eh</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Welcome to the first iteration of the Unsplash Image Fetcher, a Python script tailored for developers, designers, and content creators. This script efficiently sources and processes images based on your input theme, simplifying a task that often consumes valuable time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 1.0 Features
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Theme-Based Image Fetching:&lt;/strong&gt; Select a theme and the script fetches corresponding images from Unsplash.&lt;br&gt;
&lt;strong&gt;Resizing:&lt;/strong&gt; Automatically resizes images to 64x64 pixels.&lt;br&gt;
Aspect Ratio Selection: Option to fetch images with equal aspect ratios.&lt;br&gt;
&lt;strong&gt;Grayscale and Contrast Enhancement:&lt;/strong&gt; Converts images into high-contrast grayscale versions.&lt;br&gt;
&lt;strong&gt;Organized Saving:&lt;/strong&gt; Saves original and transformed images in separate directories for easy access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Snippet: Fetching and Resizing Images
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def fetch_images_from_unsplash(theme, config):
    """
    Fetches images from Unsplash based on the provided theme and configuration.
    If 'match_aspect_ratio' in the configuration is True, only fetches images that have an equal aspect ratio.
    Resizes the fetched images to 64x64 pixels and returns them as a list of PIL Image objects.
    """
    print(f"Fetching images for theme: {theme}")
    response = requests.get(f'https://api.unsplash.com/search/photos?query={theme}&amp;amp;client_id={UNSPLASH_ACCESS_KEY}')

    if response.status_code == 200:
        data = response.json()
        images = []
        for i, result in enumerate(data['results']):
            img_url = result['urls']['small']
            img_response = requests.get(img_url)
            img = Image.open(BytesIO(img_response.content))

            # Check if the image has an equal aspect ratio
            if not config['match_aspect_ratio'] or img.width == img.height:
                # Resize the image to 64x64 pixels
                img = img.resize((64, 64))
                images.append(img)
                print(f"Image {i+1} fetched and resized")
            else:
                print(f"Image {i+1} skipped due to unequal aspect ratio")

        print(f"Finished fetching images for theme: {theme}")
        print(f"Number of images retrieved: {len(images)}")
        if len(images) == 0:
            print(colored('No images were retrieved.', 'red'))
        print('-'*50)
        return images
    else:
        print(f"Failed to get images for theme '{theme}' from Unsplash API. Status code: {response.status_code}")
        return []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Current Limitations and Upcoming Version 2.0
&lt;/h2&gt;

&lt;p&gt;While effective, version 1.0 has its limits. It's a standalone script without a user interface, making it more suitable for those comfortable with command-line tools. Recognizing this, version 2.0 aims to include:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React App Integration:&lt;/strong&gt; Launching a user-friendly front-end.&lt;br&gt;
&lt;strong&gt;Enhanced Customization:&lt;/strong&gt; More configuration options for image processing.&lt;br&gt;
&lt;strong&gt;User Feedback Integration:&lt;/strong&gt; Implementing features based on user suggestions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Ideation for Version 2.0 Features
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Batch Processing:&lt;/strong&gt; Allow multiple themes in one go for bulk image handling.&lt;br&gt;
&lt;strong&gt;Image Filtering Options:&lt;/strong&gt; Introduce filters like brightness, contrast, and saturation adjustments.&lt;br&gt;
&lt;strong&gt;Download Quality Selection:&lt;/strong&gt; Choose the resolution for downloaded images.&lt;br&gt;
&lt;strong&gt;Interactive UI:&lt;/strong&gt; A simple and intuitive interface for non-programmers.&lt;br&gt;
&lt;strong&gt;Social Media Integration:&lt;/strong&gt; Directly upload edited images to social platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Example: Grayscale Conversion
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def convert_to_grayscale_and_contrast(image):
    """
    Converts the provided image to grayscale and applies high contrast.
    Returns the converted image.
    """
    grayscale_image = ImageOps.grayscale(image)
    high_contrast_image = ImageOps.autocontrast(grayscale_image)
    return high_contrast_image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Repository and Collaboration
&lt;/h2&gt;

&lt;p&gt;The script is available on GitHub, inviting collaboration and improvements. You're encouraged to contribute ideas, report bugs, or suggest enhancements. Check out the &lt;a href="https://github.com/MAKaminski/IconGenerator"&gt;repo&lt;/a&gt; here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Version 1.0 of the Unsplash Image Fetcher marks the beginning of a journey towards more streamlined image processing. It's a functional tool, but with your feedback and contributions, version 2.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Embark on a Journey Towards Your Goals with VisionQuestFrameworks Pt 1 of X</title>
      <dc:creator>Michael Kaminski</dc:creator>
      <pubDate>Mon, 18 Dec 2023 06:08:03 +0000</pubDate>
      <link>https://dev.to/makaminski1337/embark-on-a-journey-towards-your-goals-with-visionquestframeworks-pt-1-of-x-337l</link>
      <guid>https://dev.to/makaminski1337/embark-on-a-journey-towards-your-goals-with-visionquestframeworks-pt-1-of-x-337l</guid>
      <description>&lt;p&gt;TLDR;&lt;br&gt;
Repo Quick-Access&lt;br&gt;
Primary App: &lt;a href="https://github.com/MAKaminski/VisionQuest"&gt;VisionQuest&lt;/a&gt;&lt;br&gt;
Side-Project: &lt;a href="https://github.com/MAKaminski/data_analysis"&gt;data_analysis&lt;/a&gt;&lt;br&gt;
Extension: &lt;a href="https://marketplace.visualstudio.com/items?itemName=kaih2o.python-resource-monitor"&gt;Python Resource Monitor&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Hey there, tech enthusiasts and dream chasers! Welcome to the inaugural chapter of our weekly series on VisionQuest, a project that's about to transform the way you chase your dreams and goals. In this journey, we'll dive into the exciting world of VisionQuest, from its grand vision to the progress we've made so far.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IyUzepVB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9ay5uwhy2t6eq3whgf2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IyUzepVB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9ay5uwhy2t6eq3whgf2.png" alt="Grand Vision" width="259" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Grand Vision:&lt;/strong&gt; Your Personal AI Coach 🤖&lt;br&gt;
At the core of VisionQuest lies a bold aspiration: to create a Personal AI Coach, omni-scient, and ever present - basically clippy back from windows 95.  Ok, but we make some enhancements – an AI mentor that not only comprehends your goals but also tracks your journey and offers timely guidance to ensure your success. Whether you're striving for personal growth, professional achievements, or a healthier lifestyle, VisionQuest is your trusted companion on this path to greatness.&lt;/p&gt;

&lt;p&gt;In my case the goal of VisionQuest is to keep me guided towards my goals, with auto-coaching.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cXQAZtOY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ht7gfwaer749h2pofl5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cXQAZtOY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ht7gfwaer749h2pofl5.png" alt="Auto-Coaching" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Progress Report 🚀
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ideation Phase&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's what we're initially setting out to accomplish:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Goal Setting:&lt;/strong&gt; Our journey began with the simple act of setting and tracking goals. VisionQuest empowers you to remain laser-focused on what matters most to you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customizable Coaching Intervals:&lt;/strong&gt; We introduced coaching intervals of 2 minutes, 5 minutes, 10 minutes, or 20 minutes, letting you tailor the guidance to your pace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI-Powered Coaching:&lt;/strong&gt; Our AI algorithms are in a constant state of evolution, delivering personalized coaching that aligns with your goals and progress.&lt;/p&gt;

&lt;p&gt;The actual progress we make week-by-week will vary depending on anything we get hung-up on.  This is an educational journey, or perhaps a case-study in how VisionQuest may be able to keep a wandering mind on track.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0PwvuCwk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iaebdiae9wta5tak45h2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0PwvuCwk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iaebdiae9wta5tak45h2.png" alt="Stay Focused" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Diving into the VisionQuest Codebase
&lt;/h2&gt;

&lt;p&gt;The codebase of VisionQuest, a PyQt5-based application that interacts with the OpenAI API to generate responses to user prompts. You can check out the full code on &lt;a href="https://github.com/MAKaminski/VisionQuest/tree/main"&gt;Github&lt;/a&gt;, but I'll be sharing some juicy snippets right here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Heart of the App: MainWindow&lt;/strong&gt;&lt;br&gt;
At the heart of the application is the MainWindow class. This is where the magic happens. It's responsible for the user interface and the main functionality of the application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("VisionQuest")
        self.userName = self.getUserName()
        self.initUI()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Setting the Stage: initUI&lt;/strong&gt;&lt;br&gt;
The initUI method is where we set up the stage for our application. We're talking window flags, background, position, and transparency. We also create the chat log and send prompt sections here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def initUI(self):
    self.setWindowFlags(Qt.FramelessWindowHint)
    self.setWindowOnTop()
    self.setGradientBackground()
    self.moveWindowToTopRight()
    self.setTransparency()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Pulse: Frequency and Countdown&lt;/strong&gt;&lt;br&gt;
The createMenuBar method is where we create a menu bar with frequency options. This frequency determines how often a prompt is sent to the OpenAI API. We also have a countdown timer that ticks down to the next prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def createMenuBar(self):
    menu_bar = self.menuBar()
    self.createFrequencyMenu(menu_bar)
    self.frequency = 5  # Default frequency is 5 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Conversation Starter: sendPrompt&lt;/strong&gt;&lt;br&gt;
The sendPrompt method is where we send prompts to the OpenAI API and receive responses. We select a random prompt from a list, send it to the API, log the response, and update the chat log.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def sendPrompt(self):
    prompt = random.choice(prompts)
    response = oapi.get_openai_response(prompt)
    self.log_to_db("OpenAI_Model", response, "receiver")
    self.updateChatLog()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Memory: Chat Log&lt;/strong&gt;&lt;br&gt;
The createChatLog and updateChatLog methods are where we create and update the chat log. The chat log is stored in a SQLite database, because who doesn't love a good SQLite database?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def createChatLog(self):
    conn = sqlite3.connect('chat_log.db')
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS chat_log
                (id INTEGER PRIMARY KEY AUTOINCREMENT, time TEXT, type TEXT, username TEXT, message TEXT)''')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Interacting with OpenAI's GPT-3.5 Turbo Model
&lt;/h2&gt;

&lt;p&gt;In this section, we will look at a Python script that uses OpenAI's GPT-3.5 Turbo model to generate responses to user questions. This can be useful in a variety of applications, such as chatbots, virtual assistants, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Explanation&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;from openai import OpenAI

vision_quest_api = 'OpenAI-Key'
client = OpenAI(api_key=vision_quest_api)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start by importing the OpenAI module and initializing a client with our OpenAI API key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def get_openai_response(user_question):
    completion = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "You are a productivity enhancer, evaluating what people are doing at fixed intervals and recommending specific paths of action to help them reach their ultimate goal."},
            {"role": "user", "content": user_question}
        ]
    )

    return completion.choices[0].message.content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The get_openai_response function takes a user question as an argument. It sends a chat completion request to the GPT-3.5 Turbo model with two messages: a system message that sets the behavior of the assistant, and a user message that contains the user's question.&lt;/p&gt;

&lt;p&gt;The system message tells the model that it is a "productivity enhancer" that recommends actions to help people reach their goals. This sets the context for the conversation and guides the model's responses.&lt;/p&gt;

&lt;p&gt;The user message is the question that we want the model to answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ed4UUYcC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yec0f60tn9qcwi9ia62y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ed4UUYcC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yec0f60tn9qcwi9ia62y.png" alt="Messaging" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The function then returns the content of the model's response. This is the text that the model generated in response to the user's question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convert Images to Icons with PyQt5 and PIL
&lt;/h2&gt;

&lt;p&gt;We wanted to ensure we had a neat looking logo that matched our theme, we prompted DALE for an image - which was saved as .jpeg, then required conversion to .ico - we decided to create a utility. &lt;/p&gt;

&lt;p&gt;In this section, we will explore a simple Python script that uses PyQt5 and PIL (Python Imaging Library) to convert images to icons. This can be useful in various scenarios, such as when you're developing a desktop application and need to create an icon for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Explanation&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;from PyQt5.QtWidgets import QApplication, QFileDialog
from PIL import Image
import sys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start by importing the necessary modules. QApplication and QFileDialog from PyQt5 are used to create a simple GUI for selecting the image file. Image from PIL is used to handle the image processing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def convert_to_icon(filename):
    img = Image.open(filename)
    img = img.resize((32, 32))  # resize image to 32x32 pixels
    ico_filename = filename.rsplit('.', 1)[0] + '.ico'
    img.save(ico_filename, format='ICO', sizes=[(32,32)])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The convert_to_icon function takes a filename as an argument. It opens the image, resizes it to 32x32 pixels (the standard size for icons), and saves it in ICO format. The new icon file will have the same name as the original image file, but with the .ico extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if __name__ == "__main__":
    app = QApplication([])
    filename, _ = QFileDialog.getOpenFileName()
    if filename:
        convert_to_icon(filename)
    sys.exit(app.exec_())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the main part of the script, we create a QApplication instance and use QFileDialog.getOpenFileName() to open a file dialog for the user to select an image file. If a file is selected (i.e., if filename is not an empty string), we call convert_to_icon to convert the image to an icon. Finally, we call sys.exit(app.exec_()) to ensure a clean exit when the application is closed.&lt;/p&gt;

&lt;p&gt;This script provides a simple way to convert images to icons using PyQt5 and PIL. It can be easily extended or integrated into other projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Round-Up &amp;amp; Kickers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A Nod to Efficiency&lt;/strong&gt;&lt;br&gt;
Here's the kicker: I've integrated a resource monitor to keep an eye on memory usage. Why, you ask? Well, I'm also working on this cool data analysis project (&lt;a href="https://github.com/MAKaminski/data_analysis"&gt;check it out&lt;/a&gt;) and being resource-efficient is my jam!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZXD-IPp5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ujvovkqf34jzpuw04t5m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZXD-IPp5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ujvovkqf34jzpuw04t5m.png" alt="Resource Manager" width="634" height="657"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Name: &lt;a href="https://marketplace.visualstudio.com/items?itemName=kaih2o.python-resource-monitor"&gt;Python Resource Monitor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Look and Feel&lt;/strong&gt;&lt;br&gt;
Right now, VisionQuest rocks a transparent blue UI. It's like looking through a futuristic window where you can chat with AI. Pretty neat, huh?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--U5FClA-N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lbj8qc1iczdsfpucvk7i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--U5FClA-N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lbj8qc1iczdsfpucvk7i.png" alt="Look and Feel" width="800" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s Next? Oh, The Plans I Have!&lt;/strong&gt;&lt;br&gt;
I’m planning to split the code into neat little compartments (because organization is key), separate the UI from business logic (they need their own space), and tuck away those API keys in a safe config file.&lt;/p&gt;

&lt;p&gt;And here’s a list of touch-ups and future fireworks I'm planning:&lt;/p&gt;

&lt;p&gt;1) Get that countdown number to shine on its own in the toolbar – no more pyqt5.qtcore.qttimer gatecrashing the party.&lt;br&gt;
2) Add some cool transparency to all text boxes – it's all about that sleek look.&lt;br&gt;
3) Dress up the 'Send Prompt' button in a dapper dark blue.&lt;br&gt;
4) Fix the time calculations – let's keep it simple, no more math gymnastics.&lt;br&gt;
5) Modify frequency options to seconds in all locations&lt;br&gt;
6) On startup, VisionQuest will ask for your goals – a chat app that cares about your ambitions!&lt;br&gt;
7) And the big one: a Lucid chart mapping out multi-goals, progress measurement, and comparing your forecasted performance with actual progress.&lt;/p&gt;

&lt;p&gt;It's going to be like a control center for your aspirations!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wGLqJioC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40sro8cuszqvb6ap6kj2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wGLqJioC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/40sro8cuszqvb6ap6kj2.png" alt="Project Updates" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrapping Up&lt;/strong&gt;&lt;br&gt;
So, that's the scoop on VisionQuest. It's more than a chat app; it's my personal lab experiment where I get to mesh code, design, and AI chats into something unique.&lt;/p&gt;

&lt;p&gt;I'd love to hear your thoughts, suggestions, or any cool ideas you might have. Let's make coding not just about typing away in solitude but about sharing, learning, and growing together!&lt;/p&gt;

</description>
      <category>ui</category>
      <category>productivity</category>
      <category>python</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
