<?xml version="1.0"?><feed xmlns:media="http://search.yahoo.com/mrss/" xmlns:gr="http://www.google.com/schemas/reader/atom/" xmlns:idx="urn:atom-extension:indexing" xmlns="http://www.w3.org/2005/Atom" idx:index="no" gr:dir="ltr"><!--
Content-type: Preventing XSRF in IE.

--><generator uri="https://bazqux.com">BazQux Reader</generator><id>tag:google.com,2005:reader/feed/http://007unlicensedtotest.blogspot.com/feeds/posts/default</id><title>blogs</title><subtitle type="html">blogs</subtitle><link rel="self" href="https://bazqux.com/feed/d45a6ead98c5f8f9f99f?no_branding"></link><gr:continuation>9195524980762</gr:continuation><updated>2026-10-23T01:03:22Z</updated><entry gr:crawl-timestamp-msec="1792701690000"><id gr:original-id="https://medium.com/p/052c636daa78">tag:google.com,2005:reader/item/0000034700000086</id><title type="html">PROVING Your AI-Generated Code Works</title><published>2026-10-22T20:41:30Z</published><updated>2026-10-22T20:41:30Z</updated><link rel="alternate" href="https://jarbon.medium.com/proving-your-ai-generated-code-works-052c636daa78?source=rss-1ee8fdc3bacb------2" type="text/html"></link><summary type="html">&lt;h3&gt;Use CARBON and Lean to verify the rules your code must obey.&lt;/h3&gt;&lt;figure&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; alt src=&quot;https://cdn-images-1.medium.com/max/1024/0*flGK4Ic9YwzPsSL0.png&quot;&gt;&lt;/figure&gt;&lt;p&gt;Most developers know how to run tests. Few know that you can actually prove a software property.&lt;/p&gt;&lt;p&gt;A test asks, “Does this example work?” A proof asks, “Does this rule hold for every input in the scope we agreed to?”&lt;/p&gt;&lt;p&gt;That is a useful question when the rule controls who gets access, whether an operation is allowed, or whether an important invariant holds.&lt;/p&gt;&lt;p&gt;Lean is a language and proof checker for expressing those rules precisely and checking the reasoning behind them. A convincing explanation and a checked proof are different things. Lean checks the latter. Its validation tools also let you inspect the assumptions a proof depends on and recheck the proof artifacts. &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://lean-lang.org/doc/reference/latest/ValidatingProofs/&quot;&gt;Learn more about Lean proof validation.&lt;/a&gt;&lt;/p&gt;&lt;p&gt;CARBON Pro brings that process into your coding agent with /proof.&lt;/p&gt;&lt;p&gt;Jay, CARBON’s AI test manager, helps you choose the rule worth proving, makes the scope explicit, and brings back a result with evidence. Here is how the workflow fits together.&lt;/p&gt;&lt;h3&gt;1. Start with consequences&lt;/h3&gt;&lt;p&gt;Run /proof and Jay looks for consequential code: authorization, payments, data integrity, irreversible operations, parsers, state transitions, and logic used throughout the application.&lt;/p&gt;&lt;p&gt;The first result is a ranked shortlist, not a surprise marathon of proof attempts. Each candidate includes its location, why it matters, proposed properties, assumptions, likely approach, and difficulty. Inferred business importance is marked as uncertain.&lt;/p&gt;&lt;p&gt;You can also name a target directly:&lt;/p&gt;&lt;blockquote&gt;&lt;em&gt;Use /proof on &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;http://policy.py&quot;&gt;policy.py&lt;/a&gt;::allow. Check that an unauthenticated user can never receive access.&lt;/em&gt;&lt;/blockquote&gt;&lt;p&gt;Discovery is broad; execution is specific. The current verification backends handle a restricted set of pure Boolean Python decision functions and their Lean models. Jay identifies unsupported targets before promising a result.&lt;/p&gt;&lt;h3&gt;2. Agree on exactly what “correct” means&lt;/h3&gt;&lt;p&gt;Consider this function:&lt;/p&gt;&lt;pre&gt;def allow(authenticated: bool, owner: bool) -&amp;gt; bool:&lt;br&gt;    return authenticated and owner&lt;/pre&gt;&lt;p&gt;One useful property is:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Whenever this function grants access, the user must be authenticated.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;That is more precise than “the authorization code looks good.” It gives the checker a concrete claim to evaluate.&lt;/p&gt;&lt;p&gt;Jay presents the property, input domain, assumptions, and resource budget for approval. Here, the domain is actual Boolean values: four combinations of authenticated and owner. Caller validation is a separate responsibility.&lt;/p&gt;&lt;p&gt;Once you approve, the property stays fixed. CARBON rejects a weaker replacement claim, convenient new assumptions, or skipped inputs introduced to make the result pass.&lt;/p&gt;&lt;h3&gt;3. Run the check — and check the check&lt;/h3&gt;&lt;p&gt;CARBON has two verification paths for this supported Boolean subset.&lt;/p&gt;&lt;p&gt;The native Python path evaluates the complete input domain and independently replays the original validated function expressions through CPython.&lt;/p&gt;&lt;p&gt;The Lean path translates the decision logic into a Boolean model, constructs a deterministic proof, and performs a fresh isolated build and proof recheck. It rejects unfinished proofs and any transitive axioms in this backend. The report identifies this as a &lt;strong&gt;Lean-model proof&lt;/strong&gt;, alongside the separate production replay evidence; Python-to-Lean equivalence remains unverified.&lt;/p&gt;&lt;p&gt;Local Lean setup checks for the toolchain and, with your approval, downloads the pinned release and builds the local container. Proof execution then runs offline in isolation, with explicit resource limits.&lt;/p&gt;&lt;p&gt;The implemented search is deterministic. Jay helps frame the problem; an open-ended AI proof search is not running behind the scenes.&lt;/p&gt;&lt;h3&gt;4. Bring back an answer you can use&lt;/h3&gt;&lt;p&gt;CARBON reports four states. These are illustrative summaries, not logs from an executed run:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;PROVED&lt;/strong&gt;: The approved property holds across all four Boolean combinations under the recorded assumptions, with independent checking evidence.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;COUNTEREXAMPLE&lt;/strong&gt;: A specific input violates the property, with reproduction evidence and a runnable regression fixture.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;UNPROVED&lt;/strong&gt;: An obligation remains — for example, a resource limit was reached or the target uses unsupported semantics.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;STALE&lt;/strong&gt;: Verification inputs changed. The previous result needs a new check.&lt;/p&gt;&lt;p&gt;Now change one word in the function:&lt;/p&gt;&lt;pre&gt;return authenticated or owner&lt;/pre&gt;&lt;p&gt;The input authenticated=False, owner=True returns True. It breaks our rule: an unauthenticated user receives access.&lt;/p&gt;&lt;p&gt;For a reproduced failure, CARBON preserves the input and generates a regression fixture. When a proof cannot be established, it deliberately searches the approved domain for counterexamples within its separate allowance. An unsuccessful search remains inconclusive; it does not turn into a proof.&lt;/p&gt;&lt;p&gt;That gives you a productive outcome either way: checked evidence, a concrete failure to fix, or a clearly identified gap.&lt;/p&gt;&lt;h3&gt;5. Keep the evidence attached to the code&lt;/h3&gt;&lt;p&gt;Every proof workflow produces a CARBON HTML report with the property, scope, findings, limits, replay coverage, and links to the underlying evidence.&lt;/p&gt;&lt;p&gt;CARBON fingerprints the verification inputs — not just the function body. Dependency source, specifications, assumptions, configuration, toolchain details, and proof artifacts all matter. Changed inputs make the result stale. The incremental CI check replays approved evidence and flags changes instead of trusting an old green label.&lt;/p&gt;&lt;p&gt;The result is a precise guarantee you can inspect and maintain, rather than another reassuring paragraph from an AI.&lt;/p&gt;&lt;h3&gt;Give your most important rules stronger evidence&lt;/h3&gt;&lt;p&gt;CARBON is the testing harness with Jay, an AI test manager that lives inside your coding agent. It helps developers move from “the demo works” to evidence they can use to make a shipping decision.&lt;/p&gt;&lt;p&gt;Start with one consequential rule. Review the scope. Approve the budget. Let CARBON do the checking.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;CARBON’s core is free and open source. &lt;/strong&gt;&lt;strong&gt;/proof is included in CARBON Pro.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Get the downloads, documentation, and edition details at &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://testers.ai/carbon/&quot;&gt;testers.ai/carbon&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Jason Arbon · Testers.ai&lt;/em&gt;&lt;/p&gt;</summary><author><name>jason arbon</name></author><source gr:stream-id="feed/https://medium.com/feed/@jarbon"><id>tag:google.com,2005:reader/feed/https://medium.com/feed/@jarbon</id><title type="html">Stories by jason arbon on Medium</title><link rel="alternate" href="https://medium.com/@jarbon?source=rss-1ee8fdc3bacb------2" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792701690000"><id gr:original-id="https://medium.com/p/052c636daa78">tag:google.com,2005:reader/item/000009ff00000046</id><title type="html">PROVING Your AI-Generated Code Works</title><published>2026-10-22T20:41:30Z</published><updated>2026-10-22T20:41:30Z</updated><link rel="alternate" href="https://jarbon.medium.com/proving-your-ai-generated-code-works-052c636daa78?source=rss-1ee8fdc3bacb------2" type="text/html"></link><summary type="html">&lt;h3&gt;Use CARBON and Lean to verify the rules your code must obey.&lt;/h3&gt;&lt;figure&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; alt src=&quot;https://cdn-images-1.medium.com/max/1024/0*flGK4Ic9YwzPsSL0.png&quot;&gt;&lt;/figure&gt;&lt;p&gt;Most developers know how to run tests. Few know that you can actually prove a software property.&lt;/p&gt;&lt;p&gt;A test asks, “Does this example work?” A proof asks, “Does this rule hold for every input in the scope we agreed to?”&lt;/p&gt;&lt;p&gt;That is a useful question when the rule controls who gets access, whether an operation is allowed, or whether an important invariant holds.&lt;/p&gt;&lt;p&gt;Lean is a language and proof checker for expressing those rules precisely and checking the reasoning behind them. A convincing explanation and a checked proof are different things. Lean checks the latter. Its validation tools also let you inspect the assumptions a proof depends on and recheck the proof artifacts. &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://lean-lang.org/doc/reference/latest/ValidatingProofs/&quot;&gt;Learn more about Lean proof validation.&lt;/a&gt;&lt;/p&gt;&lt;p&gt;CARBON Pro brings that process into your coding agent with /proof.&lt;/p&gt;&lt;p&gt;Jay, CARBON’s AI test manager, helps you choose the rule worth proving, makes the scope explicit, and brings back a result with evidence. Here is how the workflow fits together.&lt;/p&gt;&lt;h3&gt;1. Start with consequences&lt;/h3&gt;&lt;p&gt;Run /proof and Jay looks for consequential code: authorization, payments, data integrity, irreversible operations, parsers, state transitions, and logic used throughout the application.&lt;/p&gt;&lt;p&gt;The first result is a ranked shortlist, not a surprise marathon of proof attempts. Each candidate includes its location, why it matters, proposed properties, assumptions, likely approach, and difficulty. Inferred business importance is marked as uncertain.&lt;/p&gt;&lt;p&gt;You can also name a target directly:&lt;/p&gt;&lt;blockquote&gt;&lt;em&gt;Use /proof on &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;http://policy.py&quot;&gt;policy.py&lt;/a&gt;::allow. Check that an unauthenticated user can never receive access.&lt;/em&gt;&lt;/blockquote&gt;&lt;p&gt;Discovery is broad; execution is specific. The current verification backends handle a restricted set of pure Boolean Python decision functions and their Lean models. Jay identifies unsupported targets before promising a result.&lt;/p&gt;&lt;h3&gt;2. Agree on exactly what “correct” means&lt;/h3&gt;&lt;p&gt;Consider this function:&lt;/p&gt;&lt;pre&gt;def allow(authenticated: bool, owner: bool) -&amp;gt; bool:&lt;br&gt;    return authenticated and owner&lt;/pre&gt;&lt;p&gt;One useful property is:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Whenever this function grants access, the user must be authenticated.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;That is more precise than “the authorization code looks good.” It gives the checker a concrete claim to evaluate.&lt;/p&gt;&lt;p&gt;Jay presents the property, input domain, assumptions, and resource budget for approval. Here, the domain is actual Boolean values: four combinations of authenticated and owner. Caller validation is a separate responsibility.&lt;/p&gt;&lt;p&gt;Once you approve, the property stays fixed. CARBON rejects a weaker replacement claim, convenient new assumptions, or skipped inputs introduced to make the result pass.&lt;/p&gt;&lt;h3&gt;3. Run the check — and check the check&lt;/h3&gt;&lt;p&gt;CARBON has two verification paths for this supported Boolean subset.&lt;/p&gt;&lt;p&gt;The native Python path evaluates the complete input domain and independently replays the original validated function expressions through CPython.&lt;/p&gt;&lt;p&gt;The Lean path translates the decision logic into a Boolean model, constructs a deterministic proof, and performs a fresh isolated build and proof recheck. It rejects unfinished proofs and any transitive axioms in this backend. The report identifies this as a &lt;strong&gt;Lean-model proof&lt;/strong&gt;, alongside the separate production replay evidence; Python-to-Lean equivalence remains unverified.&lt;/p&gt;&lt;p&gt;Local Lean setup checks for the toolchain and, with your approval, downloads the pinned release and builds the local container. Proof execution then runs offline in isolation, with explicit resource limits.&lt;/p&gt;&lt;p&gt;The implemented search is deterministic. Jay helps frame the problem; an open-ended AI proof search is not running behind the scenes.&lt;/p&gt;&lt;h3&gt;4. Bring back an answer you can use&lt;/h3&gt;&lt;p&gt;CARBON reports four states. These are illustrative summaries, not logs from an executed run:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;PROVED&lt;/strong&gt;: The approved property holds across all four Boolean combinations under the recorded assumptions, with independent checking evidence.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;COUNTEREXAMPLE&lt;/strong&gt;: A specific input violates the property, with reproduction evidence and a runnable regression fixture.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;UNPROVED&lt;/strong&gt;: An obligation remains — for example, a resource limit was reached or the target uses unsupported semantics.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;STALE&lt;/strong&gt;: Verification inputs changed. The previous result needs a new check.&lt;/p&gt;&lt;p&gt;Now change one word in the function:&lt;/p&gt;&lt;pre&gt;return authenticated or owner&lt;/pre&gt;&lt;p&gt;The input authenticated=False, owner=True returns True. It breaks our rule: an unauthenticated user receives access.&lt;/p&gt;&lt;p&gt;For a reproduced failure, CARBON preserves the input and generates a regression fixture. When a proof cannot be established, it deliberately searches the approved domain for counterexamples within its separate allowance. An unsuccessful search remains inconclusive; it does not turn into a proof.&lt;/p&gt;&lt;p&gt;That gives you a productive outcome either way: checked evidence, a concrete failure to fix, or a clearly identified gap.&lt;/p&gt;&lt;h3&gt;5. Keep the evidence attached to the code&lt;/h3&gt;&lt;p&gt;Every proof workflow produces a CARBON HTML report with the property, scope, findings, limits, replay coverage, and links to the underlying evidence.&lt;/p&gt;&lt;p&gt;CARBON fingerprints the verification inputs — not just the function body. Dependency source, specifications, assumptions, configuration, toolchain details, and proof artifacts all matter. Changed inputs make the result stale. The incremental CI check replays approved evidence and flags changes instead of trusting an old green label.&lt;/p&gt;&lt;p&gt;The result is a precise guarantee you can inspect and maintain, rather than another reassuring paragraph from an AI.&lt;/p&gt;&lt;h3&gt;Give your most important rules stronger evidence&lt;/h3&gt;&lt;p&gt;CARBON is the testing harness with Jay, an AI test manager that lives inside your coding agent. It helps developers move from “the demo works” to evidence they can use to make a shipping decision.&lt;/p&gt;&lt;p&gt;Start with one consequential rule. Review the scope. Approve the budget. Let CARBON do the checking.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;CARBON’s core is free and open source. &lt;/strong&gt;&lt;strong&gt;/proof is included in CARBON Pro.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Get the downloads, documentation, and edition details at &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://testers.ai/carbon/&quot;&gt;testers.ai/carbon&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Jason Arbon · Testers.ai&lt;/em&gt;&lt;/p&gt;</summary><author><name>jason arbon</name></author><source gr:stream-id="feed/https://jarbon.medium.com/feed"><id>tag:google.com,2005:reader/feed/https://jarbon.medium.com/feed</id><title type="html">Stories by jason arbon on Medium</title><link rel="alternate" href="https://medium.com/@jarbon?source=rss-1ee8fdc3bacb------2" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792699373000"><id gr:original-id="https://medium.com/p/a6e13716dba1">tag:google.com,2005:reader/item/000008af00000042</id><category term="software-engineering"></category><category term="ai"></category><category term="bugs"></category><category term="php"></category><category term="security"></category><title type="html">The Bug That Hid in 70% of the Web for 10+ Years, and How AI Found It</title><published>2026-10-22T20:02:53Z</published><updated>2026-10-22T20:02:53Z</updated><link rel="alternate" href="https://medium.com/@anshita.bhasin/the-bug-that-hid-in-70-of-the-web-for-10-years-and-how-ai-found-it-a6e13716dba1?source=rss-cf4a02363eb1------2" type="text/html"></link><summary type="html">&lt;figure&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; alt src=&quot;https://cdn-images-1.medium.com/max/1024/1*vBtmKfC7HM8SFWWU0cYIkQ.png&quot;&gt;&lt;/figure&gt;&lt;p&gt;On October 21, 2026, websites around the world started showing “maintenance mode” pages. Government, school, and hospital sites in Europe went offline on purpose, and companies like Nintendo took some services down too.&lt;/p&gt;&lt;p&gt;The reason was not a hack of “the internet”. It was two critical bugs in PHP, the software that runs behind a huge part of the web. Together they are called &lt;strong&gt;PHP2Shell&lt;/strong&gt;, and they had been sitting in PHP’s code for more than ten years. AI helped find them.&lt;/p&gt;&lt;h3&gt;First, what is PHP?&lt;/h3&gt;&lt;p&gt;PHP is a programming language that runs on web servers. When you open a website, PHP is often the part that takes your request, talks to the database, and builds the page you see. WordPress, Drupal, and Joomla all run on it. Wiz Research says PHP powers about 70% of websites.&lt;/p&gt;&lt;p&gt;Think of a website as a hotel:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;You&lt;/strong&gt; are the guest walking in.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;The hotel’s rooms&lt;/strong&gt; are the website’s data: accounts, orders, passwords.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;PHP is the receptionist.&lt;/strong&gt; Every guest goes through the front desk first. The receptionist reads your request, checks the register, and sends you where you need to go.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The website owner hires the receptionist, but does not train them. PHP comes ready-made. So if the receptionist has a bad habit, every hotel that uses them has the same problem. That is why this news is so big: the bug is in PHP itself, not in one website’s code.&lt;/p&gt;&lt;h3&gt;The receptionist’s two bad habits&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Bad habit 1: Panicking when the desk is full&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;When PHP runs out of memory, it stops what it is doing immediately. Sometimes it stops while a guest’s form is only half filled in. Later, during clean-up, PHP treats that half-filled form as if it were complete and correct. An attacker can send a specially built upload request that pushes PHP into exactly this situation. On servers using Apache with mod_php, this can happen before the website’s own code even runs.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Bad habit 2: Not taking the old key back&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;PHP keeps some notes from one guest’s visit after that guest has left and their space has been cleared. If the next guest is served by the same worker, PHP can use those stale notes before resetting them. It is like a hotel where the last guest’s room key still opens the door for the next person.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Why it matters&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;By using these habits, an attacker can make the server run their own commands. No username or password is needed. Wiz also says attackers could read passwords, tokens, and API keys from PHP’s memory.&lt;/p&gt;&lt;p&gt;The PHP security team rated both bugs &lt;strong&gt;9.2 out of 10&lt;/strong&gt; (CVSS v4.0, “critical”). A score of 10.0 went around on social media before the release; the PHP Foundation says that number was incorrect.&lt;/p&gt;&lt;h3&gt;Hidden for over a decade, found with AI&lt;/h3&gt;&lt;p&gt;Every PHP version from &lt;strong&gt;5.4 up to 8.5.11&lt;/strong&gt; is affected. Wiz says every PHP version released in the past decade carries at least one of the two bugs, and they survived years of manual security reviews.&lt;/p&gt;&lt;p&gt;So why now? Two teams found the problem using AI:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Calif:&lt;/strong&gt; researcher Ryan Austin used OpenAI’s GPT-5.6 Sol during an August 2026 audit of PHP’s source code. The audit was funded by OpenAI under its “Patch the Planet” programme, which pairs AI models with human security experts to find and fix bugs in open-source software.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Wiz Research&lt;/strong&gt; independently found and reported both bugs, also with help from AI models. Wiz says proving the bugs could actually be exploited needed a chain of steps so complex it sat at the edge of what human researchers could practically do.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Volker Dusch, a security engineer at the nonprofit that oversees PHP, called it the worst security problem the project has faced in more than ten years and compared it to Heartbleed, according to The Wall Street Journal.&lt;/p&gt;&lt;p&gt;The important point: AI did not create these bugs. They were already there. AI helped security researchers find them, so PHP could build a fix.&lt;/p&gt;&lt;h3&gt;What happened this week&lt;/h3&gt;&lt;p&gt;The plan was a quiet, coordinated fix. Instead, the bug IDs leaked a day early and attackers started probing.&lt;/p&gt;&lt;figure&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; alt src=&quot;https://cdn-images-1.medium.com/max/561/1*yxhJ4jLcsyu_6yFVtqpHCA.png&quot;&gt;&lt;/figure&gt;&lt;p&gt;The EU, the US, Germany, Japan, Australia, the UK and the Netherlands all issued local warnings, according to &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://therecord.media/php-script-vulnerabilities-warnings&quot;&gt;The Record&lt;/a&gt;.&lt;/p&gt;&lt;h3&gt;What you should do&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;If you run a website on PHP&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Update PHP to &lt;strong&gt;8.5.12, 8.4.27, 8.3.36 or 8.2.35&lt;/strong&gt;. If you install PHP through your operating system’s package manager, apply your vendor’s updated packages (&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://it.uw.edu/help/servicestatus/php-cves/&quot;&gt;UW-IT&lt;/a&gt;).&lt;/li&gt;&lt;li&gt;On older branches: PHP released minimal source backports for 7.4, 8.0, and 8.1 (&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.helpnetsecurity.com/2026/10/21/php2shell-php-vulnerabilities-patched-cve-2026-109618-cve-2026-109619/&quot;&gt;Help Net Security&lt;/a&gt;). watchTowr notes there are no patches for PHP 5 or 7 overall (&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://watchtowr.com/intelligence/php-cve-2026-109618-faq/&quot;&gt;watchTowr&lt;/a&gt;).&lt;/li&gt;&lt;li&gt;On shared hosting, ask your provider to confirm the fix is &lt;strong&gt;installed&lt;/strong&gt;, not just scheduled (&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.malwarebytes.com/blog/bugs/2026/10/php2shell-why-websites-are-going-offline-for-emergency-security-updates&quot;&gt;Malwarebytes&lt;/a&gt;).&lt;/li&gt;&lt;li&gt;If you take a site down while patching, Google’s John Mueller advises returning HTTP 503 rather than letting pages time out or return 404/500 (&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.seroundtable.com/google-php2shell-42353.html&quot;&gt;Search Engine Roundtable&lt;/a&gt;).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;If your site is behind Cloudflare&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Cloudflare’s CTO Dane Knecht said PHP sites behind Cloudflare already have an initial set of firewall (WAF) rules for the known exploit paths, on every plan including Free (&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.helpnetsecurity.com/2026/10/21/php2shell-php-vulnerabilities-patched-cve-2026-109618-cve-2026-109619/&quot;&gt;Help Net Security&lt;/a&gt;). Keep those rules on, but they are an extra layer only. Because the bug lives in PHP itself, you still have to update PHP.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;If you are just browsing&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The fix is applied on web servers, not on your phone or laptop. Some sites may stay in maintenance mode for a while as their teams patch (&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.malwarebytes.com/blog/bugs/2026/10/php2shell-why-websites-are-going-offline-for-emergency-security-updates&quot;&gt;Malwarebytes&lt;/a&gt;).&lt;/p&gt;&lt;h3&gt;The lesson for testers: test the failure path&lt;/h3&gt;&lt;p&gt;Look at where both bugs lived. Not in login, search, or checkout. They lived in what PHP does &lt;strong&gt;when things go wrong&lt;/strong&gt;: running out of memory, hitting a fatal error, cleaning up between requests.&lt;/p&gt;&lt;p&gt;Most test suites are built around the happy path. We check that the right input gives the right output. Far fewer tests ask:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;What happens when the server runs out of memory halfway through a request?&lt;/li&gt;&lt;li&gt;What state is left behind after a crash, and does the next request see it?&lt;/li&gt;&lt;li&gt;What does the system do with a huge or oddly formatted upload?&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;That is exactly the kind of corner where this bug hid for over a decade. Wiz says AI models lowered the barrier to proving bugs like this are exploitable, so these corners deserve more of our testing time, not less.&lt;/p&gt;&lt;p&gt;One small step this week: add one “what if it breaks halfway?” test to your suite.&lt;/p&gt;&lt;h3&gt;Myths vs facts&lt;/h3&gt;&lt;figure&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; alt src=&quot;https://cdn-images-1.medium.com/max/440/1*3dH_VRbtetoCbDFQAQl9gA.png&quot;&gt;&lt;/figure&gt;&lt;p&gt;If you want to stay updated on tech &amp;amp; AI updates, follow me on other platforms:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Anshita Bhasin&lt;/strong&gt;&lt;br&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.instagram.com/abautomationhub_/&quot;&gt;Instagram&lt;/a&gt; | &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.linkedin.com/in/anshita-bhasin/&quot;&gt;LinkedIn&lt;/a&gt; | &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://twitter.com/anshita__bhasin&quot;&gt;Twitter&lt;/a&gt; | &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.youtube.com/channel/UCtNHEcO_c9qpaUqIYSb98Rg&quot;&gt;Youtube&lt;/a&gt;&lt;/p&gt;</summary><author><name>Anshita Bhasin</name></author><source gr:stream-id="feed/https://medium.com/feed/@anshita.bhasin"><id>tag:google.com,2005:reader/feed/https://medium.com/feed/@anshita.bhasin</id><title type="html">Stories by Anshita Bhasin on Medium</title><link rel="alternate" href="https://medium.com/@anshita.bhasin?source=rss-cf4a02363eb1------2" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792695127000"><id gr:original-id="https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa">tag:google.com,2005:reader/item/00000a2700000071</id><title type="html">RAG ou fine-tuning? A pergunta certa é outra</title><published>2026-10-22T18:52:07Z</published><updated>2026-10-22T18:52:07Z</updated><link rel="alternate" href="https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa" type="text/html"></link><summary type="html">&lt;p&gt;Até pouco tempo atrás, classificar o motivo de uma inadimplência era trabalho de gente lendo. O analista lia as cinco últimas tentativas registradas pelo call center, consultava a política de cobrança — um PDF de cento e oitenta páginas que mudava a cada trimestre e que ninguém lia inteiro — e escolhia um código numa lista de quarenta e dois. De três a seis minutos por caso. Numa carteira de quarenta mil eventos por dia a conta não fecha, então o que acontecia era outra coisa: uma regra fixa pegava os casos óbvios, o resto caía num balde chamado &amp;quot;outros&amp;quot;, e o balde crescia ano após ano até virar metade da carteira.&lt;/p&gt;&lt;p&gt;A alternativa clássica era treinar um classificador. Funcionava — e ainda funciona — mas exigia uma base rotulada que ninguém tinha e três meses que a diretoria não dava. E, quando o produto mudava, começava tudo de novo.&lt;/p&gt;&lt;p&gt;O que mudou não foi a inteligência disponível. Foi o custo de usá-la. Um modelo de linguagem&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-1&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-1&quot;&gt;1&lt;/a&gt; lê os mesmos cinco registros de contato, a mesma cláusula da política, e devolve o código — em dois segundos, por uma fração de centavo, sem base rotulada e sem projeto de três meses. &lt;mark data-color=&quot;#fff2cc&quot; style=&quot;background-color: rgb(255, 242, 204); color: rgb(0, 0, 0)&quot;&gt;A precisão que antes exigia um especialista hoje está a uma chamada de API de distância.&lt;/mark&gt;&lt;/p&gt;&lt;p&gt;E é exatamente aí que começa o problema que este artigo quer resolver. Porque assim que o time entende que dá para fazer, aparece a pergunta errada: &lt;em&gt;&lt;strong&gt;&lt;mark data-color=&quot;#fff2cc&quot; style=&quot;background-color: rgb(255, 242, 204); color: rgb(0, 0, 0)&quot;&gt;a gente usa RAG ou faz fine-tuning?&lt;/mark&gt;&lt;/strong&gt;&lt;/em&gt; Essa pergunta trata duas coisas diferentes como se fossem concorrentes, e o custo de errar a resposta não é teórico — é uma fatura mensal.&lt;/p&gt;&lt;p&gt;A pergunta certa é outra, e ela cabe numa linha só:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Isto aqui muda com que frequência, e eu preciso provar de onde veio a resposta?&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;É essa. Não &amp;quot;qual é melhor&amp;quot;, mas o que esse pedaço do seu problema faz ao longo do tempo e o que você vai precisar mostrar depois. Guarde a frase — ela volta no fim, e é honestamente a única coisa daqui que você precisa levar desse artigo para seu time de engenharia.&lt;/p&gt;&lt;p&gt;Este texto serve a quem escreve o código e a quem assina não escreve mas toma decisões de negócio. Toda parte técnica vem com tradução ao lado, e todo termo que costuma aparecer sem explicação tem nota de rodapé no fim. Se você nunca treinou um modelo na vida, relaxa: dá para acompanhar tudo. E no fim você sai com uma conta de guardanapo que resolve a discussão na sua mesa.&lt;/p&gt;&lt;h2&gt;Tópico 1 — Onde o conhecimento mora&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Um modelo de linguagem guarda conhecimento em dois lugares com propriedades opostas, e cada estratégia mexe num deles.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Pense no plantonista do pronto-socorro. O que ele aprendeu na faculdade está na cabeça dele: vale para qualquer paciente, não muda durante o plantão, e custou seis anos para entrar ali. O que ele sabe sobre o paciente do leito 12 está no prontuário preso na prancheta: entrou agora, é específico daquele caso, e se ele largar a prancheta na mesa, esqueceu tudo.&lt;/p&gt;&lt;p&gt;O modelo é igual. O que ele aprendeu no treinamento está nos &lt;strong&gt;pesos&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-2&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-2&quot;&gt;2&lt;/a&gt; — milhões ou bilhões de números fixos. O que ele sabe sobre o seu caso está na &lt;strong&gt;janela de contexto&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-3&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-3&quot;&gt;3&lt;/a&gt;, o texto que você manda junto com a pergunta. Terminou a chamada, a prancheta sumiu. O modelo não lembra da conversa anterior; o que chamamos de memória é só o histórico sendo reenviado inteiro a cada chamada, e pago de novo a cada chamada.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!w-Ca!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0eb5880-a9ad-4d69-b064-d57d74b8fe3e_3400x1380.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!w-Ca!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0eb5880-a9ad-4d69-b064-d57d74b8fe3e_3400x1380.png 424w, https://substackcdn.com/image/fetch/$s_!w-Ca!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0eb5880-a9ad-4d69-b064-d57d74b8fe3e_3400x1380.png 848w, https://substackcdn.com/image/fetch/$s_!w-Ca!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0eb5880-a9ad-4d69-b064-d57d74b8fe3e_3400x1380.png 1272w, https://substackcdn.com/image/fetch/$s_!w-Ca!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0eb5880-a9ad-4d69-b064-d57d74b8fe3e_3400x1380.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;974&quot; height=&quot;395&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/e0eb5880-a9ad-4d69-b064-d57d74b8fe3e_3400x1380.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:1380,&amp;quot;width&amp;quot;:3400,&amp;quot;resizeWidth&amp;quot;:974,&amp;quot;bytes&amp;quot;:327440,&amp;quot;alt&amp;quot;:&amp;quot;Figura 1. Os dois reservatórios de conhecimento de um modelo e suas propriedades opostas. A escolha entre RAG e fine-tuning é a escolha de qual reservatório você vai abastecer.&amp;quot;,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:null,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt=&quot;Figura 1. Os dois reservatórios de conhecimento de um modelo e suas propriedades opostas. A escolha entre RAG e fine-tuning é a escolha de qual reservatório você vai abastecer.&quot; title=&quot;Figura 1. Os dois reservatórios de conhecimento de um modelo e suas propriedades opostas. A escolha entre RAG e fine-tuning é a escolha de qual reservatório você vai abastecer.&quot; data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!w-Ca!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0eb5880-a9ad-4d69-b064-d57d74b8fe3e_3400x1380.png 424w, https://substackcdn.com/image/fetch/$s_!w-Ca!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0eb5880-a9ad-4d69-b064-d57d74b8fe3e_3400x1380.png 848w, https://substackcdn.com/image/fetch/$s_!w-Ca!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0eb5880-a9ad-4d69-b064-d57d74b8fe3e_3400x1380.png 1272w, https://substackcdn.com/image/fetch/$s_!w-Ca!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0eb5880-a9ad-4d69-b064-d57d74b8fe3e_3400x1380.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!w-Ca!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0eb5880-a9ad-4d69-b064-d57d74b8fe3e_3400x1380.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Figura 1. Os dois reservatórios de conhecimento de um modelo e suas propriedades opostas. A escolha entre RAG e fine-tuning é a escolha de qual reservatório você vai abastecer.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;A analogia já falha num ponto, e é melhor eu estragar isso agora do que deixar você descobrir em produção: a prancheta tem tamanho, e você paga por cada token&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-4&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-4&quot;&gt;4&lt;/a&gt; que enfia nela. Em português a conta é pior — o tokenizador&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-5&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-5&quot;&gt;5&lt;/a&gt; da maioria dos modelos foi otimizado para inglês, e uma palavra em português consome de 1,5 a 2 tokens contra cerca de 1,3 em inglês. Na prática, o mesmo texto custa de 15% a 50% mais caro em português do que em inglês.&lt;/p&gt;&lt;p&gt;Guarde essa frase, porque ela é o eixo de tudo que vem a seguir: &lt;strong&gt;RAG mexe no prontuário. Fine-tuning mexe na formação do médico.&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;Tópico 2 — RAG por dentro: a matemática da busca por significado&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;RAG não é um modelo; é uma busca bem-feita grampeada na frente de uma chamada ao modelo.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;RAG&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-6&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-6&quot;&gt;6&lt;/a&gt; significa geração aumentada por recuperação, e a ideia cabe numa frase: antes de perguntar ao modelo, procure nos seus documentos os trechos que importam e cole-os no prompt. O modelo não precisa ter decorado a política de cobrança. Ele precisa ter a cláusula relevante na frente dos olhos na hora de responder.&lt;/p&gt;&lt;p&gt;A parte não óbvia é &lt;em&gt;como&lt;/em&gt; você acha o trecho certo. Busca por palavra-chave não serve: o usuário pergunta &amp;quot;posso parcelar&amp;quot;, a cláusula fala em &amp;quot;fracionamento do débito&amp;quot;, e o &lt;code&gt;LIKE &amp;apos;%parcelar%&amp;apos;&lt;/code&gt; volta vazio. O que resolve é busca por significado.&lt;/p&gt;&lt;h3&gt;A matemática, em dois minutos&lt;/h3&gt;&lt;p&gt;Pense na gôndola do supermercado. &lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!XcaZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde4d7f79-b799-4b18-8674-e66000263548_1140x500.jpeg&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!XcaZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde4d7f79-b799-4b18-8674-e66000263548_1140x500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!XcaZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde4d7f79-b799-4b18-8674-e66000263548_1140x500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!XcaZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde4d7f79-b799-4b18-8674-e66000263548_1140x500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!XcaZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde4d7f79-b799-4b18-8674-e66000263548_1140x500.jpeg 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;1140&quot; height=&quot;500&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/de4d7f79-b799-4b18-8674-e66000263548_1140x500.jpeg&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:null,&amp;quot;imageSize&amp;quot;:null,&amp;quot;height&amp;quot;:500,&amp;quot;width&amp;quot;:1140,&amp;quot;resizeWidth&amp;quot;:null,&amp;quot;bytes&amp;quot;:null,&amp;quot;alt&amp;quot;:null,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:null,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:null,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:null,&amp;quot;offset&amp;quot;:false}&quot; alt data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!XcaZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde4d7f79-b799-4b18-8674-e66000263548_1140x500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!XcaZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde4d7f79-b799-4b18-8674-e66000263548_1140x500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!XcaZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde4d7f79-b799-4b18-8674-e66000263548_1140x500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!XcaZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde4d7f79-b799-4b18-8674-e66000263548_1140x500.jpeg 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!XcaZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde4d7f79-b799-4b18-8674-e66000263548_1140x500.jpeg&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Você não acha o achocolatado procurando pela letra A: acha porque ele está perto do leite em pó, que está perto do café. A loja organiza por significado, não por ordem alfabética — e por isso você encontra sem saber o nome exato do produto.&lt;/p&gt;&lt;p&gt;Um &lt;strong&gt;embedding&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-7&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-7&quot;&gt;7&lt;/a&gt; faz isso com texto. Cada trecho vira um ponto num mapa, e textos que falam da mesma coisa ficam vizinhos. O mapa não tem duas dimensões como a planta do supermercado — tem centenas ou milhares — mas a matemática é a mesma que você aprendeu no ensino médio.&lt;/p&gt;&lt;p&gt;Vou fazer a conta com você, usando só duas dimensões para caber no papel. Imagine que a primeira dimensão mede &amp;quot;quanto o texto fala de acordo entre as partes&amp;quot; e a segunda, &amp;quot;quanto fala de taxa de juros&amp;quot;. Três frases viram três pontos:&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:null}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;&amp;quot;renegociação de dívida&amp;quot;  -&amp;gt; A = [0.90, 0.40]
&amp;quot;acordo de pagamento&amp;quot;     -&amp;gt; B = [0.85, 0.45]
&amp;quot;variação da taxa Selic&amp;quot;  -&amp;gt; C = [0.10, 0.95]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Para quem não programa: cada frase virou um par de coordenadas, tipo um endereço num mapa. Falta agora uma régua que diga quais endereços são &amp;quot;do mesmo bairro&amp;quot;. Essa régua é a &lt;strong&gt;similaridade de cosseno&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-8&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-8&quot;&gt;8&lt;/a&gt;: ela mede o ângulo entre dois pontos vistos da origem. Ângulo pequeno, assunto parecido. A fórmula é a razão entre o produto escalar e o produto dos comprimentos:&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:null}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;cos(A,B) = (A · B) / (|A| × |B|)

A · B  = 0.90×0.85 + 0.40×0.45 = 0.765 + 0.180 = 0.945
|A|    = raiz(0.90² + 0.40²)   = raiz(0.97)    = 0.985
|B|    = raiz(0.85² + 0.45²)   = raiz(0.925)   = 0.962

cos(A,B) = 0.945 / (0.985 × 0.962) = 0.998&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Praticamente 1 — e as duas frases não compartilham &lt;em&gt;nenhuma palavra&lt;/em&gt;. Agora a terceira:&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:null}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;A · C  = 0.90×0.10 + 0.40×0.95 = 0.090 + 0.380 = 0.470
|C|    = raiz(0.10² + 0.95²)   = raiz(0.9125)  = 0.955

cos(A,C) = 0.470 / (0.985 × 0.955) = 0.500&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Metade: a régua separou. Um embedding real tem de 768 a 3.072 dimensões e os números saem de um modelo treinado, não da sua mão — mas a conta que o banco executa milhões de vezes por dia é literalmente essa. Não tem mágica ali.&lt;/p&gt;&lt;h3&gt;O passo a passo da implementação&lt;/h3&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!qdeJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9694adc8-b803-4555-bcb4-862770c5e9d1_3400x1480.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!qdeJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9694adc8-b803-4555-bcb4-862770c5e9d1_3400x1480.png 424w, https://substackcdn.com/image/fetch/$s_!qdeJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9694adc8-b803-4555-bcb4-862770c5e9d1_3400x1480.png 848w, https://substackcdn.com/image/fetch/$s_!qdeJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9694adc8-b803-4555-bcb4-862770c5e9d1_3400x1480.png 1272w, https://substackcdn.com/image/fetch/$s_!qdeJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9694adc8-b803-4555-bcb4-862770c5e9d1_3400x1480.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;892&quot; height=&quot;388&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/9694adc8-b803-4555-bcb4-862770c5e9d1_3400x1480.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:1480,&amp;quot;width&amp;quot;:3400,&amp;quot;resizeWidth&amp;quot;:892,&amp;quot;bytes&amp;quot;:330303,&amp;quot;alt&amp;quot;:&amp;quot;Figura 2. As duas metades de um pipeline de RAG. A indexação roda quando o documento muda; a consulta roda a cada pergunta. É essa separação que torna a atualização barata.&amp;quot;,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:null,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt=&quot;Figura 2. As duas metades de um pipeline de RAG. A indexação roda quando o documento muda; a consulta roda a cada pergunta. É essa separação que torna a atualização barata.&quot; title=&quot;Figura 2. As duas metades de um pipeline de RAG. A indexação roda quando o documento muda; a consulta roda a cada pergunta. É essa separação que torna a atualização barata.&quot; data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!qdeJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9694adc8-b803-4555-bcb4-862770c5e9d1_3400x1480.png 424w, https://substackcdn.com/image/fetch/$s_!qdeJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9694adc8-b803-4555-bcb4-862770c5e9d1_3400x1480.png 848w, https://substackcdn.com/image/fetch/$s_!qdeJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9694adc8-b803-4555-bcb4-862770c5e9d1_3400x1480.png 1272w, https://substackcdn.com/image/fetch/$s_!qdeJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9694adc8-b803-4555-bcb4-862770c5e9d1_3400x1480.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!qdeJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9694adc8-b803-4555-bcb4-862770c5e9d1_3400x1480.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Figura 2. As duas metades de um pipeline de RAG. A indexação roda quando o documento muda; a consulta roda a cada pergunta. É essa separação que torna a atualização barata.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ingestão.&lt;/strong&gt; Texto limpo d as fontes: política, FAQ, histórico de atendimento, cláusulas. Lixo entra, lixo sai — e PDF escaneado sem OCR é lixo.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chunking&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-9&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-9&quot;&gt;9&lt;/a&gt;. Pedaços de 400 a 800 tokens, com 10% a 20% de sobreposição. Se o corte cair no meio da cláusula, a condição fica num pedaço e a exceção no outro, e o modelo responde com meia verdade. Corte por estrutura — seção, artigo, parágrafo — não por contagem de caracteres.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Embedding.&lt;/strong&gt; Cada pedaço vira vetor. Use um modelo que entenda português de verdade: a diferença de qualidade entre modelos é maior em português que em inglês.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Armazenamento.&lt;/strong&gt; Grave os vetores com os metadados que você vai querer filtrar depois: produto, versão da política, data de vigência, segmento. São eles que impedem o sistema de citar uma política revogada.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Recuperação.&lt;/strong&gt; Na pergunta, gerar o vetor e buscar os &lt;em&gt;k&lt;/em&gt; vizinhos mais próximos&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-10&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-10&quot;&gt;10&lt;/a&gt; — k entre 3 e 10 na maioria dos casos. Combine com busca por palavra-chave (busca híbrida&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-11&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-11&quot;&gt;11&lt;/a&gt;): nomes próprios, números de contrato e códigos de produto são exatamente onde a busca semântica erra.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Re-ranking&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-12&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-12&quot;&gt;12&lt;/a&gt;. Passar os candidatos por um modelo menor que reordena por relevância real. Barato, e costuma ser o passo com melhor retorno por hora investida em todo o pipeline.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Montagem do prompt.&lt;/strong&gt; Cole os trechos com a fonte identificada e instrua o modelo a responder &lt;em&gt;apenas&lt;/em&gt; com base neles — e a dizer que não sabe quando não bastarem. Essa instrução é metade da defesa contra alucinação&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-13&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-13&quot;&gt;13&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avaliação.&lt;/strong&gt; Sem um conjunto de perguntas com resposta conhecida, nenhum dos sete passos acima é ajustável. É o assunto da próxima seção.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h3&gt;Qual decisão você consegue desfazer&lt;/h3&gt;&lt;p&gt;Repare numa coisa: dos oito passos acima, dois você refaz num sábado e dois te prendem por um ano. Saber qual é qual vale mais do que acertar de primeira.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Barato de desfazer:&lt;/strong&gt; tamanho do pedaço, valor de &lt;em&gt;k&lt;/em&gt;, texto do prompt e re-ranking. Tudo configuração — você muda, roda a avaliação e compara. Se o time está travando a escolha de chunking numa reunião, já gastou em reunião mais do que custaria testar as três opções.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Caro de desfazer:&lt;/strong&gt; o modelo de embedding. Trocá-lo obriga a re-embeddar o acervo inteiro, reconstruir o índice e recalibrar o &lt;em&gt;k&lt;/em&gt; — e vetores de modelos diferentes não são comparáveis entre si, então não existe migração parcial nem convivência das duas versões. Essa é a decisão que merece meio dia de teste com os &lt;em&gt;seus&lt;/em&gt; documentos, não a escolha do que estava no topo do ranking que você leu ontem.&lt;/p&gt;&lt;p&gt;E a métrica que arbitra as duas: num conjunto de 50 a 200 perguntas com resposta conhecida, meça &lt;strong&gt;recuperação&lt;/strong&gt; (o trecho certo entrou nos &lt;em&gt;k&lt;/em&gt;?) separada de &lt;strong&gt;fidelidade&lt;/strong&gt; (a resposta usou o trecho que entrou?). Recuperação baixa é problema de busca; fidelidade baixa com recuperação alta é problema de prompt. Quem olha só a taxa de acerto geral fica meses ajustando o lado errado.&lt;/p&gt;&lt;p&gt;As ferramentas de cada passo — o que são, para que servem e como instalar — estão no apêndice, no fim do artigo.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Pense comigo&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Se alguém perguntar hoje &amp;quot;de onde saiu essa resposta&amp;quot;, seu sistema consegue devolver o documento, a versão e o parágrafo — ou só a resposta?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Quando a política de cobrança muda, quantas pessoas precisam agir para o sistema passar a usar a versão nova? Se a resposta for mais que uma, o custo real da sua solução não está na fatura da API.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Você já mediu quantas vezes o trecho certo &lt;em&gt;foi&lt;/em&gt; recuperado e o modelo respondeu errado assim mesmo? É um número diferente da taxa de acerto geral, e é o que diz se o problema está na busca ou no prompt.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/blockquote&gt;&lt;h2&gt;Tópico 3 — Fine-tuning por dentro: a matemática do ajuste&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Fine-tuning muda como o modelo responde, não o que ele sabe — e confundir as duas coisas é o erro mais caro desta área.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Fine-tuning&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-14&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-14&quot;&gt;14&lt;/a&gt; é ajustar os pesos do modelo com os seus exemplos. Você mostra mil pares de &amp;quot;entrada que eu tenho&amp;quot; e &amp;quot;saída que eu queria&amp;quot;, e o treinamento empurra os números internos na direção que reduz o erro&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-15&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-15&quot;&gt;15&lt;/a&gt;. O resultado é um modelo que, sem que você peça nada no prompt, já responde no seu formato, com o seu jargão e com o seu critério.&lt;/p&gt;&lt;p&gt;Treinar todos os pesos de um modelo moderno é caro e, na maior parte dos casos, desnecessário. Foi daí que saiu a técnica que tornou isso viável para empresas normais.&lt;/p&gt;&lt;h3&gt;LoRA: o que é&lt;/h3&gt;&lt;p&gt;Você não reimprime as novecentas páginas do manual porque três procedimentos mudaram. Você publica uma errata de quatro páginas e manda grampear no fim. Quem consulta usa o manual original mais a errata, e o resultado é o manual corrigido.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;LoRA&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-16&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-16&quot;&gt;16&lt;/a&gt; — &lt;em&gt;Low-Rank Adaptation&lt;/em&gt;, adaptação de posto&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-17&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-17&quot;&gt;17&lt;/a&gt; baixo — é a errata dos pesos. Em vez de reescrever a matriz original, ela congela o modelo inteiro e aprende um ajuste pequeno, guardado à parte, que é somado por cima na hora de responder.&lt;/p&gt;&lt;p&gt;Três consequências saem direto dessa escolha, e são elas que explicam por que LoRA virou o padrão. Você treina muito menos números, então cabe numa GPU alugada em vez de num cluster. O que você guarda no fim não é um modelo, é um arquivo de poucos megabytes — versionável, revisável, fácil de reverter. E, como o ajuste é somado de volta à matriz original antes de servir, o modelo em produção continua sendo um modelo só: não há camada extra, nem um milissegundo a mais por resposta.&lt;/p&gt;&lt;h3&gt;De onde surgiu&lt;/h3&gt;&lt;p&gt;LoRA nasceu de um problema de conta de infraestrutura, não de uma teoria elegante. Em junho de 2021, Edward Hu e colegas da Microsoft publicaram o artigo que a descreve. O contexto era o GPT-3 de 175 bilhões de parâmetros, e o impasse era simples de enunciar: fazer fine-tuning daquele modelo produzia &lt;em&gt;outro&lt;/em&gt; modelo de 175 bilhões de parâmetros. Uma cópia inteira por tarefa. Treinar exigia na casa de 1,2 TB de memória de vídeo&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-18&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-18&quot;&gt;18&lt;/a&gt;, e cada checkpoint salvo ocupava algo como 350 GB. Imagine agora que você quer um modelo especializado por cliente. A conta do armazenamento, sozinha, já mata o projeto antes de ele começar.&lt;/p&gt;&lt;p&gt;A aposta teórica veio de uma pesquisa anterior sobre &lt;strong&gt;dimensão intrínseca&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-19&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-19&quot;&gt;19&lt;/a&gt;, e aqui vale parar um segundo porque é contraintuitivo. Trabalhos de 2018 e 2020 mostraram que modelos com bilhões de parâmetros, na prática, se movimentam num espaço muito menor do que o tamanho deles sugere. É como um mapa de mil ruas onde todo mundo só usa trinta. Dá para projetar o treino nesse espaço menor e ainda assim aprender bem. Hu e os coautores deram um passo a mais: se o modelo vive num espaço pequeno, então &lt;em&gt;a diferença&lt;/em&gt; entre o modelo antes e depois do ajuste também cabe num espaço pequeno. Ou seja, você não precisa escrever essa diferença por inteiro — basta escrevê-la comprimida.&lt;/p&gt;&lt;p&gt;Já existia um concorrente para o mesmo problema, as &lt;strong&gt;camadas adaptadoras&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-20&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-20&quot;&gt;20&lt;/a&gt; de 2019: pequenos blocos novos enfiados entre as camadas do modelo, treinados enquanto o resto fica congelado. Funcionavam, e pela mesma razão — poucos parâmetros novos. Mas tinham um defeito que condenava a ideia em produção: por serem blocos extras, eles rodam &lt;em&gt;em sequência&lt;/em&gt; com o resto da rede, e isso cobra tempo em toda inferência. Nos testes do próprio artigo da LoRA, com lote de tamanho um, os adaptadores acrescentavam entre 20% e 30% de latência. Vinte por cento a mais em cada resposta, para sempre, é um preço que ninguém quer pagar.&lt;/p&gt;&lt;p&gt;Aqui está a sacada que fez LoRA vencer. Como o ajuste dela é uma multiplicação de matrizes do mesmo formato da matriz original, ele pode ser &lt;em&gt;somado de volta&lt;/em&gt; aos pesos antes de publicar o modelo. Treina-se separado; serve-se junto. O custo em latência não é baixo: é zero. Os números que o artigo reporta para o GPT-3 saem daí — até dez mil vezes menos parâmetros treináveis, cerca de um terço da memória de GPU, e o checkpoint de 350 GB virando um arquivo de 35 MB.&lt;/p&gt;&lt;h3&gt;A matemática do ajuste&lt;/h3&gt;&lt;p&gt;Para quem não programa: uma camada do modelo é uma tabela quadrada de números. Numa dimensão típica de 4.096, essa tabela tem 4.096 linhas por 4.096 colunas:&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:null}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;W : 4096 x 4096 = 16.777.216 números  (congelado)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;LoRA diz: não toque em &lt;code&gt;W&lt;/code&gt;. Aprenda o ajuste como o produto de duas tabelas magras — uma alta e estreita, outra baixa e larga — com uma largura &lt;em&gt;r&lt;/em&gt; bem pequena, tipicamente 8, 16 ou 64:&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:null}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;A : 4096 x 16  =    65.536 números  (treinado)
B :   16 x 4096 =   65.536 números  (treinado)
                  --------------
                     131.072 números

16.777.216 / 131.072 = 128&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cento e vinte e oito vezes menos parâmetros para treinar. E a fórmula geral é tão simples que dá para fazer de cabeça: a economia é &lt;code&gt;d / (2r)&lt;/code&gt;, onde &lt;em&gt;d&lt;/em&gt; é a dimensão da camada e &lt;em&gt;r&lt;/em&gt; a largura que você escolheu. Dobre o &lt;em&gt;r&lt;/em&gt;, corte a economia pela metade. É o único hiperparâmetro cuja consequência de custo você consegue calcular antes de rodar.&lt;/p&gt;&lt;p&gt;E qual &lt;em&gt;r&lt;/em&gt; basta? Menos do que a intuição sugere. No artigo original, boa parte dos resultados usa &lt;code&gt;r = 4&lt;/code&gt;, e os autores observam que para o GPT-3 valores de 1 ou 2 já funcionam — num modelo cuja dimensão cheia é 12.288. É a hipótese da dimensão intrínseca aparecendo na prática: a correção de rumo que você precisa é mesmo pequena. Vale notar também &lt;em&gt;onde&lt;/em&gt; a errata é aplicada: nos experimentos principais, apenas nas matrizes de consulta e de valor do mecanismo de atenção — nem sequer no modelo inteiro.&lt;/p&gt;&lt;p&gt;Na prática, isso vira uma regra de bolso: comece em &lt;code&gt;r = 8&lt;/code&gt; ou &lt;code&gt;16&lt;/code&gt;, suba só se a avaliação mostrar que o modelo não está conseguindo aprender a tarefa. Se você precisou chegar a 128 ou 256 para o número subir, pare e reavalie — ou a tarefa não era de forma, era de conhecimento (e aí é RAG), ou o modelo base é pequeno demais para ela.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!tOVJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F348772ff-dbf2-465e-a9d0-08cd219ddbd0_3400x1520.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!tOVJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F348772ff-dbf2-465e-a9d0-08cd219ddbd0_3400x1520.png 424w, https://substackcdn.com/image/fetch/$s_!tOVJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F348772ff-dbf2-465e-a9d0-08cd219ddbd0_3400x1520.png 848w, https://substackcdn.com/image/fetch/$s_!tOVJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F348772ff-dbf2-465e-a9d0-08cd219ddbd0_3400x1520.png 1272w, https://substackcdn.com/image/fetch/$s_!tOVJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F348772ff-dbf2-465e-a9d0-08cd219ddbd0_3400x1520.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;890&quot; height=&quot;397&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/348772ff-dbf2-465e-a9d0-08cd219ddbd0_3400x1520.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:1520,&amp;quot;width&amp;quot;:3400,&amp;quot;resizeWidth&amp;quot;:890,&amp;quot;bytes&amp;quot;:271860,&amp;quot;alt&amp;quot;:&amp;quot;Figura 3. A decomposição de posto baixo do LoRA. A economia é exatamente d dividido por 2r, e é calculável antes de reservar a GPU.&amp;quot;,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:null,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt=&quot;Figura 3. A decomposição de posto baixo do LoRA. A economia é exatamente d dividido por 2r, e é calculável antes de reservar a GPU.&quot; title=&quot;Figura 3. A decomposição de posto baixo do LoRA. A economia é exatamente d dividido por 2r, e é calculável antes de reservar a GPU.&quot; data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!tOVJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F348772ff-dbf2-465e-a9d0-08cd219ddbd0_3400x1520.png 424w, https://substackcdn.com/image/fetch/$s_!tOVJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F348772ff-dbf2-465e-a9d0-08cd219ddbd0_3400x1520.png 848w, https://substackcdn.com/image/fetch/$s_!tOVJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F348772ff-dbf2-465e-a9d0-08cd219ddbd0_3400x1520.png 1272w, https://substackcdn.com/image/fetch/$s_!tOVJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F348772ff-dbf2-465e-a9d0-08cd219ddbd0_3400x1520.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!tOVJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F348772ff-dbf2-465e-a9d0-08cd219ddbd0_3400x1520.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Figura 3. A decomposição de posto baixo do LoRA. A economia é exatamente d dividido por 2r, e é calculável antes de reservar a GPU.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;h3&gt;Para que serve — e quando não serve&lt;/h3&gt;&lt;p&gt;Por que funciona, afinal? Porque o ajuste é muito mais simples que o conhecimento que já está lá. O modelo já sabe português, já sabe o que é uma dívida, já sabe estruturar uma resposta. Falta uma correção de rumo — e correção de rumo cabe em pouca informação.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Serve para três coisas, bem concretas.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;A primeira é especializar sem multiplicar infraestrutura. Como o adaptador é um arquivo pequeno e separado, você pode manter &lt;em&gt;um&lt;/em&gt; modelo base carregado na memória e trocar o adaptador conforme a chamada — um para classificar motivo de inadimplência, outro para resumir atendimento, outro para o tom de um cliente específico. É a diferença entre operar um servidor de GPU e operar sete.&lt;/p&gt;&lt;p&gt;A segunda é tornar o treino um evento ordinário de engenharia. Um adaptador de modelo pequeno treina em horas numa GPU alugada e produz um artefato de megabytes. Isso cabe no seu fluxo normal: versionar, revisar, promover entre ambientes, reverter quando der errado. Fine-tuning deixa de ser um projeto de ciência de dados e vira mais um artefato de build.&lt;/p&gt;&lt;p&gt;A terceira é não pagar por isso em latência. Fundido o adaptador, o modelo servido é indistinguível de um modelo treinado por inteiro — do ponto de vista de quem chama, não existe LoRA ali.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;E não serve para duas, igualmente concretas.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Não serve para injetar conhecimento — é o mesmo alerta do quadro acima, agora com o mecanismo explicado: um ajuste de posto baixo não tem capacidade para guardar fatos novos de forma confiável. Ele muda a direção da resposta, não o acervo do modelo.&lt;/p&gt;&lt;p&gt;E não serve quando o domínio é genuinamente estranho ao modelo base. Se o modelo nunca viu nada parecido com o seu assunto, a correção que você precisa não é de rumo, é de formação — e aí nenhum &lt;em&gt;r&lt;/em&gt; razoável dá conta. O sinal é o que descrevi acima: você só consegue resultado subindo o posto até o ponto em que a errata fica do tamanho do manual. Nesse ponto, a resposta honesta é trocar o modelo base, não insistir no adaptador.&lt;/p&gt;&lt;p&gt;Uma nota de atualidade, porque a técnica não parou em 2021: a variante mais usada hoje é a &lt;strong&gt;QLoRA&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-21&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-21&quot;&gt;21&lt;/a&gt;, que congela o modelo base em precisão reduzida (4 bits) antes de treinar o adaptador. O efeito prático é que modelos que exigiriam várias GPUs passam a caber numa só. Para quem está começando, é o caminho de menor atrito.&lt;/p&gt;&lt;h3&gt;Antes de treinar: a escada&lt;/h3&gt;&lt;p&gt;Antes de você reservar GPU, uma ressalva importante: fine-tuning é o quarto degrau, não o primeiro. A ordem importa porque cada degrau custa mais e é menos reversível que o anterior. Subir sem medir é como trocar o banco de dados antes de olhar o plano de execução da query — você pode até acertar, mas foi sorte.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prompt bem escrito.&lt;/strong&gt; Instrução explícita, formato de saída declarado, critério de desempate para os casos ambíguos. Custo: uma tarde. Resolve mais do que a maioria dos times admite.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Exemplos dentro do prompt.&lt;/strong&gt; De três a dez casos já resolvidos, colados na instrução. Custo: outra tarde, mais tokens por chamada. É fine-tuning de pobre — e às vezes é suficiente.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;RAG.&lt;/strong&gt; Quando o que falta é informação, não comportamento. O teste é direto: se a resposta errada melhora quando você cola o documento certo no prompt, o seu problema é de busca, e treinar não vai resolver.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fine-tuning.&lt;/strong&gt; Quando os três anteriores foram medidos, o número parou de subir, e o que ainda falta é consistência de forma ou economia em volume alto.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;O teste para saber se você pode pular direto ao degrau quatro é um só: &lt;em&gt;você tem o número do degrau anterior anotado em algum lugar?&lt;/em&gt; Se não tem, o fine-tuning não vai consertar nada — vai esconder o problema atrás de um modelo novo, e aí o diagnóstico fica mais caro, não mais barato.&lt;/p&gt;&lt;h3&gt;O passo a passo da implementação&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Definir a tarefa com borda nítida.&lt;/strong&gt; &amp;quot;Classificar o motivo de não-pagamento em um de 42 códigos&amp;quot; é tarefa de fine-tuning. &amp;quot;Responder dúvidas sobre cobrança&amp;quot; não é — é tarefa de RAG disfarçada.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Montar o conjunto de exemplos.&lt;/strong&gt; De 500 a 5.000 pares bem revisados batem 100 mil pares sujos, sempre. A qualidade do rótulo é o teto da qualidade do modelo, e não existe hiperparâmetro que conserte rótulo errado.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Formatar.&lt;/strong&gt; JSONL&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-22&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-22&quot;&gt;22&lt;/a&gt;, um exemplo por linha, no formato de conversa que o modelo escolhido espera. Separe de 10% a 20% para avaliação e &lt;em&gt;não&lt;/em&gt; deixe esses exemplos vazarem para o treino.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Estabelecer a linha de base.&lt;/strong&gt; O número que você já mediu nos degraus anteriores. É o que o fine-tuning precisa bater para se justificar.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treinar com LoRA.&lt;/strong&gt; Comece com &lt;em&gt;r&lt;/em&gt; = 16, alpha = 32, 2 ou 3 épocas&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-23&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-23&quot;&gt;23&lt;/a&gt; e taxa de aprendizado&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-24&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-24&quot;&gt;24&lt;/a&gt; na casa de 1e-4. Para um modelo de 7 a 8 bilhões de parâmetros, isso roda em poucas horas numa única GPU alugada.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avaliar contra a linha de base.&lt;/strong&gt; No conjunto separado, não no de treino. E verifique regressão: é comum o modelo ficar ótimo na tarefa e pior em tudo mais ao redor dela.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Servir.&lt;/strong&gt; Funda o adaptador&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-25&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-25&quot;&gt;25&lt;/a&gt; nos pesos, ou mantenha-o separado e carregue sob demanda — o segundo caminho serve vários adaptadores especializados sobre uma única cópia do modelo base.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Agendar o próximo treino.&lt;/strong&gt; Fine-tuning não é entrega, é dependência: quando o produto mudar, o modelo precisa de nova rodada. Isso entra no orçamento do time no dia zero.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Também aqui as ferramentas estão no apêndice. Vale adiantar a ordem de grandeza do custo: um treino LoRA de modelo pequeno numa GPU alugada por hora custa de dezenas a poucas centenas de dólares — um almoço de time, não um projeto.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;A frase que economiza dinheiro&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Fine-tuning não injeta fatos de forma confiável. Se você treinar o modelo com a política de cobrança esperando que ele &amp;quot;decore&amp;quot; as regras, vai obter algo pior que não saber: um modelo que erra com convicção, no tom certo, no formato certo, sem nenhum sinal de que errou. Fatos vão para o contexto. Comportamento vai para os pesos.&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;Tópico 4 — A conta&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;A economia não está no preço do token; está no preço do token multiplicado pela frequência — e é a frequência que decide a estratégia.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Antes da conta, o terreno. Em outubro de 2026 todo fornecedor repete as mesmas três faixas: um modelo de ponta entre dois e dez dólares por milhão de tokens de entrada, com a saída custando de três a cinco vezes a entrada; um intermediário cerca de dez vezes mais barato, e uma faixa econômica cotada em centavos por milhão. É com essas faixas que eu vou calcular. &lt;strong&gt;Os preços vão mudar; a estrutura da conta, não.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;E vamos usar a carteira de cobrança lá do começo do artigo: 40 mil eventos por dia, 1,2 milhão por mês, classificando o motivo de não-pagamento a partir do histórico de contato. Pegue papel, que são duas contas curtas.&lt;/p&gt;&lt;h3&gt;Cenário A — RAG sobre modelo de ponta&lt;/h3&gt;&lt;p&gt;Seis trechos recuperados mais a instrução: ~3.500 tokens de entrada e 150 de saída por chamada.&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:null}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;entrada: 1.200.000 x 3.500 = 4.200.000.000 tokens = 4.200 M
         4.200 M x US$ 2,00/M  = US$ 8.400
saída:   1.200.000 x   150 =   180.000.000 tokens =   180 M
           180 M x US$ 10,00/M = US$ 1.800
                                 -----------
                        total:   US$ 10.200 / mês&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3&gt;Cenário B — modelo pequeno com fine-tuning&lt;/h3&gt;&lt;p&gt;O &amp;quot;como classificar&amp;quot; virou peso, então o prompt encolhe para 400 tokens: sem instrução, sem exemplos, sem trechos recuperados.&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:null}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;entrada: 1.200.000 x   400 =   480.000.000 tokens =   480 M
           480 M x US$ 0,15/M = US$ 72
saída:     180 M x US$ 0,60/M = US$ 108
                                 -----------
                        total:   US$ 180 / mês
                        treino:  ~US$ 300, uma vez por trimestre&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dez mil e duzentos contra cento e oitenta. &lt;strong&gt;Cinquenta e seis vezes.&lt;/strong&gt; O treino se paga em menos de um dia de operação. Se o seu caso é esse, não existe discussão a ter.&lt;/p&gt;&lt;p&gt;Mas aqui eu preciso ser honesto sobre o número, porque ele favorece a minha tese por um motivo que não é o da minha tese. O cenário A também não carrega o índice vetorial nem o re-ranking na fatura, o que torna a diferença ainda maior do que o número mostra. Mas o ponto central é outro: essa economia existe porque a tarefa é &lt;em&gt;classificação estável&lt;/em&gt;: 42 códigos que mudam uma vez por ano. Troque a tarefa por &amp;quot;explicar ao cliente qual condição de parcelamento se aplica ao contrato dele hoje&amp;quot; e o cenário B vira uma máquina de dar resposta errada com segurança — e um acordo oferecido fora da alçada custa, num único caso, mais do que os dez mil dólares que você economizou no mês inteiro.&lt;/p&gt;&lt;h3&gt;A fronteira, em uma fórmula&lt;/h3&gt;&lt;p&gt;Em linguagem comum: a conta abaixo diz quantos meses o fine-tuning leva para se pagar.&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:null}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;meses_para_pagar = custo_do_treino / (volume_mensal x economia_por_chamada)

e o treino só vale a pena se:
meses_para_pagar  &amp;lt;  meses_ate_a_tarefa_mudar&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;O denominador tem volume. O limite tem estabilidade. É por isso que a pergunta &amp;quot;qual é melhor&amp;quot; não tem resposta: ela ignora as duas variáveis que decidem.&lt;/p&gt;&lt;p&gt;E agora o que a fórmula &lt;em&gt;não&lt;/em&gt; vê, que é onde a maioria das decisões de fine-tuning azeda. O custo do treino não é a GPU: é o conjunto de avaliação que alguém precisa manter vivo, a rodada de retreino a cada mudança de produto, e mais uma versão de modelo para rastrear quando a resposta sair errada em produção às duas da manhã. Numa conta honesta, some ao numerador algumas horas de engenharia por trimestre. Se o seu time não tem quem assuma isso nominalmente, o payback de um dia vira passivo permanente.&lt;/p&gt;&lt;p&gt;Falta também a dimensão que não aparece em dólar: &lt;strong&gt;latência&lt;/strong&gt;. Um modelo pequeno com prompt de 400 tokens responde numa fração do tempo de um modelo de ponta com 3.500 tokens de contexto para ler. Se o seu caso é lote noturno, isso não vale nada. Se é uma tela de atendimento com o cliente na linha, pode valer mais que a economia inteira — e aí a decisão deixa de ser sobre fatura.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!tooC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feedc4f2a-1b93-4bec-9334-2ce3f54c1ae9_3400x1480.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!tooC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feedc4f2a-1b93-4bec-9334-2ce3f54c1ae9_3400x1480.png 424w, https://substackcdn.com/image/fetch/$s_!tooC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feedc4f2a-1b93-4bec-9334-2ce3f54c1ae9_3400x1480.png 848w, https://substackcdn.com/image/fetch/$s_!tooC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feedc4f2a-1b93-4bec-9334-2ce3f54c1ae9_3400x1480.png 1272w, https://substackcdn.com/image/fetch/$s_!tooC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feedc4f2a-1b93-4bec-9334-2ce3f54c1ae9_3400x1480.png 1456w&quot; sizes=&quot;100vw&quot;&gt;&lt;img width=&quot;912&quot; height=&quot;396&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/eedc4f2a-1b93-4bec-9334-2ce3f54c1ae9_3400x1480.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:1480,&amp;quot;width&amp;quot;:3400,&amp;quot;resizeWidth&amp;quot;:912,&amp;quot;bytes&amp;quot;:350588,&amp;quot;alt&amp;quot;:&amp;quot;Figura 4. As três curvas de custo. O fine-tuning parte de um patamar por causa do treino e cresce devagar; o modelo de ponta parte do zero e cresce rápido. O ponto de virada é a única coisa que você precisa estimar.&amp;quot;,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:null,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt=&quot;Figura 4. As três curvas de custo. O fine-tuning parte de um patamar por causa do treino e cresce devagar; o modelo de ponta parte do zero e cresce rápido. O ponto de virada é a única coisa que você precisa estimar.&quot; title=&quot;Figura 4. As três curvas de custo. O fine-tuning parte de um patamar por causa do treino e cresce devagar; o modelo de ponta parte do zero e cresce rápido. O ponto de virada é a única coisa que você precisa estimar.&quot; data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!tooC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feedc4f2a-1b93-4bec-9334-2ce3f54c1ae9_3400x1480.png 424w, https://substackcdn.com/image/fetch/$s_!tooC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feedc4f2a-1b93-4bec-9334-2ce3f54c1ae9_3400x1480.png 848w, https://substackcdn.com/image/fetch/$s_!tooC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feedc4f2a-1b93-4bec-9334-2ce3f54c1ae9_3400x1480.png 1272w, https://substackcdn.com/image/fetch/$s_!tooC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feedc4f2a-1b93-4bec-9334-2ce3f54c1ae9_3400x1480.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!tooC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feedc4f2a-1b93-4bec-9334-2ce3f54c1ae9_3400x1480.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Figura 4. As três curvas de custo. O fine-tuning parte de um patamar por causa do treino e cresce devagar; o modelo de ponta parte do zero e cresce rápido. O ponto de virada é a única coisa que você precisa estimar.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Três alavancas que mudam a conta antes de qualquer decisão de arquitetura, e que muita gente deixa na mesa:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Processamento em lote&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-26&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-26&quot;&gt;26&lt;/a&gt;. Se a resposta não precisa ser imediata — e classificação de carteira noturna não precisa — a maioria dos fornecedores dá cerca de 50% de desconto. Metade da fatura, por uma mudança de desenho, não de modelo.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Cache de prompt&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-27&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-27&quot;&gt;27&lt;/a&gt;. Quando o começo do prompt é sempre igual (sua instrução, suas regras, seus exemplos fixos), ele pode ser cobrado uma vez e reaproveitado. Isso exige que a parte estável venha &lt;em&gt;antes&lt;/em&gt; da parte variável — uma decisão de ordenação de string que vale milhares de dólares por mês.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Destilação&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-28&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-28&quot;&gt;28&lt;/a&gt;. O terceiro caminho, e onde mora a economia mais bonita. Você usa o modelo caro para gerar alguns milhares de exemplos de alta qualidade, treina o modelo pequeno com eles e aposenta o caro no caminho de alto volume. É o cenário B sem meses de rotulação humana: o modelo grande vira ferramenta de fábrica, não peça de produção.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Pense comigo&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Você sabe qual é o volume mensal de chamadas do seu caso de uso mais caro, ou só o valor total da fatura? Sem separar por caminho, não dá para saber onde otimizar.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Quanto do seu prompt é idêntico em todas as chamadas? Essa fração é desconto de cache esperando para ser cobrado.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Qual é o custo de um erro no seu domínio? Enquanto esse número não estiver na mesa, a discussão sobre modelo mais barato está sendo feita com metade da informação.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/blockquote&gt;&lt;h2&gt;Tópico 5 — O critério: o que muda, o que é estável, e o que precisa de prova&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;A pergunta da abertura tem duas metades, e nenhuma delas é sobre tecnologia.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Lembra dela? &lt;em&gt;Isto aqui muda com que frequência, e eu preciso provar de onde veio a resposta?&lt;/em&gt; A primeira metade mede &lt;strong&gt;velocidade de mudança&lt;/strong&gt;. A segunda mede &lt;strong&gt;necessidade de prova&lt;/strong&gt;. São eixos independentes, então cruze os dois e a escolha para de ser opinião: vira um quadrante com quatro respostas, e cada uma delas aponta para um lugar diferente.&lt;/p&gt;&lt;p&gt;Vamos aos quatro.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Muda rápido e precisa de prova.&lt;/strong&gt; Política vigente, condição contratual, saldo devedor, norma do regulador. RAG, sem discussão. Fine-tuning aqui é tecnicamente possível e profissionalmente irresponsável: você grava nos pesos uma regra que vence no mês que vem, sem jeito de mostrar a fonte.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Não muda e não precisa de prova.&lt;/strong&gt; Formato de saída, tom, jargão do domínio, classificação em taxonomia fechada. Território natural do fine-tuning — dá para fazer com instrução no prompt também, mas aí você paga aqueles tokens em toda chamada, para sempre.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Não muda mas precisa de prova.&lt;/strong&gt; Cálculo de juros, régua de negativação, critérios de alçada. Aqui a resposta raramente é um modelo: é código determinístico que o modelo &lt;em&gt;chama&lt;/em&gt;. Regra estável e auditável merece teste unitário, não treino.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Muda rápido e não precisa de prova.&lt;/strong&gt; Resumo de atendimento, priorização de fila, rascunho de mensagem. RAG leve, ou nem isso — às vezes é só um prompt bom.&lt;/p&gt;&lt;h3&gt;A auditabilidade não é um detalhe de conformidade&lt;/h3&gt;&lt;p&gt;Esse eixo decide sozinho uma quantidade enorme de casos no nosso domínio. O AI Act&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-29&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-29&quot;&gt;29&lt;/a&gt; europeu classifica pontuação de crédito como alto risco, com transparência e conformidade obrigatórias desde agosto de 2026. No Brasil, LGPD, normas do Banco Central e o direito do consumidor à explicação de decisão automatizada apontam para o mesmo lugar por caminhos diferentes.&lt;/p&gt;&lt;p&gt;Um modelo com fine-tuning responde &amp;quot;código 17, inadimplência por desemprego&amp;quot;. Você não tem como mostrar de onde saiu — a justificativa está diluída nos milhões de números que o treino ajustou, espalhados por todas as camadas. Um sistema com RAG responde a mesma coisa e anexa o trecho do atendimento e a cláusula da política que sustentam o código.&lt;/p&gt;&lt;p&gt;Numa área onde a decisão afeta o acesso de alguém ao crédito, poder apontar a fonte não é um bônus de engenharia. É requisito de operação.&lt;/p&gt;&lt;h3&gt;E a resposta que ninguém quer ouvir&lt;/h3&gt;&lt;p&gt;Na prática, os sistemas que funcionam fazem as duas coisas. O fine-tuning ensina o formato, o jargão e o critério; o RAG entrega o fato do dia. O modelo pequeno treinado sabe &lt;em&gt;como&lt;/em&gt; classificar e o contexto recuperado diz &lt;em&gt;o que&lt;/em&gt; aconteceu. Não é escolha excludente; é divisão de trabalho.&lt;/p&gt;&lt;p&gt;Mas, se eu puder deixar só um conselho: a maioria dos times não precisa de fine-tuning nenhum. Precisa de um RAG bem construído e de um conjunto de avaliação de verdade. Fine-tuning é otimização — e otimizar antes de medir continua sendo o erro mais antigo da nossa profissão, com roupa nova.&lt;/p&gt;&lt;h3&gt;O que fazer na segunda-feira&lt;/h3&gt;&lt;p&gt;Se você só for levar uma coisa daqui, leve esta sequência. Ela cabe em duas semanas e substitui aquela reunião em que o time discute RAG contra fine-tuning sem um número sequer na mesa — você sabe exatamente de que reunião eu estou falando.&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Escolha um caso de uso só&lt;/strong&gt;, o de maior volume. Anote duas coisas: quantas chamadas por mês ele faz e quanto custa, em reais, um erro dele.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monte 50 perguntas com resposta conhecida.&lt;/strong&gt; Esse arquivo é o ativo mais valioso do projeto e o único que não dá para comprar pronto. Tudo que vem depois mede contra ele.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Meça o degrau um&lt;/strong&gt; — modelo de ponta, prompt bem escrito, nada mais. Esse número é a sua linha de base para o resto do ano, e metade das vezes ele já é bom o bastante.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Classifique o que falta&lt;/strong&gt; nos quatro quadrantes acima. O que muda rápido vai para RAG; o que é estável e precisa de prova vira código determinístico; o resto espera.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Só então&lt;/strong&gt; faça a conta do tópico 4, com tokens medidos e não estimados. Se o payback não couber dentro do prazo de validade da tarefa, você acabou de economizar um trimestre.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;Voltando ao analista e ao PDF de cento e oitenta páginas&lt;/h2&gt;&lt;p&gt;Ele não desaparece. Muda o que ele faz com as horas que gastava lendo. O balde de &amp;quot;outros&amp;quot; para de crescer, e a política que ninguém lia inteira passa a ser consultada corretamente quarenta mil vezes por dia.&lt;/p&gt;&lt;p&gt;Nada disso depende de escolher a tecnologia da moda. Depende de olhar para cada pedaço do problema e fazer aquela pergunta sem glamour nenhum: &lt;em&gt;Isto aqui muda com que frequência, e eu preciso provar de onde veio a resposta?&lt;/em&gt;&lt;/p&gt;&lt;p&gt;A engenharia boa quase sempre começa separando o que muda do que não muda. Nesse sentido, não mudou nada — só o nome das ferramentas.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Na sua operação, qual é a parte que todo mundo trata como estável e que na verdade muda toda semana? Conta nos comentários — eu desconfio que a resposta mais comum vai ser &amp;quot;a política&amp;quot;.&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;Apêndice — as ferramentas, uma a uma&lt;/h2&gt;&lt;p&gt;Esta parte é de consulta, não de leitura corrida — pode favoritar e voltar depois. Cada ferramenta vem com três coisas: o que ela é, para que serve na prática e quando você pode pular sem culpa. Comandos e nomes de pacote em inglês, como sempre.&lt;/p&gt;&lt;h3&gt;Para montar um RAG&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;pgvector&lt;/strong&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; data-component-name=&quot;FootnoteAnchorToDOM&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-anchor-30&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-30&quot;&gt;30&lt;/a&gt; — extensão do PostgreSQL que guarda vetores e faz busca por proximidade. &lt;em&gt;Serve para:&lt;/em&gt; ser o seu banco vetorial sem adicionar banco nenhum ao inventário. &lt;em&gt;Pule se:&lt;/em&gt; você já passou de alguns milhões de vetores com exigência de latência baixa — e só depois de medir.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Qdrant&lt;/strong&gt; — banco vetorial dedicado, roda como container. &lt;em&gt;Serve para:&lt;/em&gt; volume alto, filtros complexos por metadado e controle fino do índice. &lt;em&gt;Pule se:&lt;/em&gt; o pgvector ainda dá conta; é infraestrutura a mais para operar.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Spring AI&lt;/strong&gt; — abstração de modelos, embeddings e bancos vetoriais para Java/Spring Boot. &lt;em&gt;Serve para:&lt;/em&gt; manter o RAG dentro do mesmo stack do motor, com injeção de dependência e configuração que o time já entende. &lt;em&gt;Pule se:&lt;/em&gt; a casa é Python.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;LangChain&lt;/strong&gt; e &lt;strong&gt;LlamaIndex&lt;/strong&gt; — bibliotecas Python de orquestração. A primeira é mais geral; a segunda nasceu focada em indexação e recuperação. &lt;em&gt;Servem para:&lt;/em&gt; não escrever à mão o encadeamento de carregar, picar, embeddar, buscar e montar o prompt. &lt;em&gt;Pule se:&lt;/em&gt; o seu pipeline tem quatro passos; nesse caso a biblioteca pesa mais que o problema.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;sentence-transformers&lt;/strong&gt; — biblioteca para rodar modelos de embedding na sua própria máquina. &lt;em&gt;Serve para:&lt;/em&gt; usar modelos abertos como &lt;em&gt;BGE-M3&lt;/em&gt; ou &lt;em&gt;E5 multilingual&lt;/em&gt;, que vão bem em português, quando o dado não pode sair da sua infraestrutura. &lt;em&gt;Pule se:&lt;/em&gt; você pode chamar um embedding hospedado — é mais simples e você não opera GPU.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reranker&lt;/strong&gt; — um modelo menor que reordena por relevância os trechos que a busca trouxe. Existem versões abertas (a família &lt;em&gt;bge-reranker&lt;/em&gt;) e hospedadas. &lt;em&gt;Serve para:&lt;/em&gt; o melhor retorno por hora investida em todo o pipeline. &lt;em&gt;Pule se:&lt;/em&gt; você ainda não mediu a recuperação — sem o número, não dá para saber se melhorou.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ragas&lt;/strong&gt; e &lt;strong&gt;DeepEval&lt;/strong&gt; — bibliotecas de avaliação de RAG. &lt;em&gt;Servem para:&lt;/em&gt; dar prontas as métricas de recuperação e de fidelidade à fonte, em cima do seu conjunto de perguntas. &lt;em&gt;Pule se:&lt;/em&gt; você prefere escrever as duas métricas à mão, o que é perfeitamente razoável e leva uma tarde.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Para quem não programa: os comandos abaixo baixam e instalam esses pacotes. A primeira linha sobe um PostgreSQL já com o pgvector dentro; as demais instalam bibliotecas Python.&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:null}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=dev pgvector/pgvector:pg17
# then, inside the database: CREATE EXTENSION vector;

docker run -d -p 6333:6333 qdrant/qdrant

pip install langchain llama-index
pip install sentence-transformers
pip install ragas deepeval&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3&gt;Para fazer fine-tuning&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;APIs gerenciadas&lt;/strong&gt; (OpenAI, Vertex AI, Bedrock) — você sobe o JSONL e recebe um modelo treinado. &lt;em&gt;Servem para:&lt;/em&gt; a primeira tentativa, sempre. Mais caro por rodada, muito mais barato em tempo de gente. &lt;em&gt;Pule se:&lt;/em&gt; o dado não pode sair da sua infraestrutura.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;transformers&lt;/strong&gt; — a biblioteca base da Hugging Face para carregar e rodar modelos. &lt;em&gt;Serve para:&lt;/em&gt; ser a fundação de todo o resto nesta lista.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;peft&lt;/strong&gt; — é onde o LoRA mora, literalmente. &lt;em&gt;Serve para:&lt;/em&gt; aplicar o adaptador de posto baixo sobre um modelo congelado, escolher o &lt;em&gt;r&lt;/em&gt; e salvar o resultado como arquivo separado. &lt;em&gt;Pule se:&lt;/em&gt; você usa Unsloth ou Axolotl, que já o embrulham.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;trl&lt;/strong&gt; — a camada de treino supervisionado em cima do peft. &lt;em&gt;Serve para:&lt;/em&gt; rodar o laço de treino sem escrevê-lo.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;bitsandbytes&lt;/strong&gt; — quantização. &lt;em&gt;Serve para:&lt;/em&gt; habilitar a QLoRA, congelando o modelo base em 4 bits para que ele caiba numa GPU só. &lt;em&gt;Pule se:&lt;/em&gt; o modelo já cabe sem isso.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unsloth&lt;/strong&gt; — empacotamento otimizado do treino LoRA/QLoRA. &lt;em&gt;Serve para:&lt;/em&gt; treinar bem mais rápido e com menos memória, com poucas linhas. &lt;em&gt;É o caminho de menor atrito para a primeira rodada local.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Axolotl&lt;/strong&gt; e &lt;strong&gt;LLaMA-Factory&lt;/strong&gt; — treino dirigido por arquivo de configuração. &lt;em&gt;Servem para:&lt;/em&gt; tornar o treino reproduzível e versionável: o YAML vai para o repositório junto com o dataset. &lt;em&gt;Pule se:&lt;/em&gt; você está só experimentando.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Em linguagem comum: a primeira linha abaixo instala o conjunto básico de treino; a segunda, o atalho otimizado; a terceira, o treino por configuração.&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:null}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;pip install transformers peft trl datasets accelerate bitsandbytes

pip install unsloth

pip install axolotl&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3&gt;Para servir e observar&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;vLLM&lt;/strong&gt; — servidor de inferência de alta vazão. &lt;em&gt;Serve para:&lt;/em&gt; colocar o seu modelo em produção com throughput sério, e carregar vários adaptadores LoRA sobre uma única cópia do modelo base — exatamente o cenário que torna o LoRA interessante em operação. &lt;em&gt;Pule se:&lt;/em&gt; você está usando modelo hospedado por API.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ollama&lt;/strong&gt; — roda modelos na sua máquina com um comando. &lt;em&gt;Serve para:&lt;/em&gt; testar antes de gastar com infraestrutura, e para desenvolver offline. &lt;em&gt;Pule se:&lt;/em&gt; seu notebook não tem memória para o modelo que você quer testar.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Langfuse&lt;/strong&gt; e &lt;strong&gt;Phoenix&lt;/strong&gt; — observabilidade de aplicações com modelo. &lt;em&gt;Servem para:&lt;/em&gt; rastrear, em cada chamada, qual prompt foi montado, quais trechos foram recuperados e o que o modelo respondeu. Sem isso você não depura: você adivinha. &lt;em&gt;Pule se:&lt;/em&gt; nunca — este é o item que mais gente deixa para depois e mais faz falta no primeiro incidente.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div data-attrs=&quot;{&amp;quot;language&amp;quot;:&amp;quot;plaintext&amp;quot;,&amp;quot;nodeId&amp;quot;:null}&quot; data-component-name=&quot;HighlightedCodeBlockToDOM&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;pip install vllm
pip install langfuse arize-phoenix
# Ollama: installer at &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;http://ollama.com&quot;&gt;ollama.com&lt;/a&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3&gt;O mínimo para começar amanhã&lt;/h3&gt;&lt;p&gt;Se você quer sair daqui com uma única linha de comando e um caminho, é este: um Postgres com pgvector para o índice, um modelo hospedado para gerar e embeddar, e Langfuse ligado desde a primeira chamada. Nada de GPU, nada de treino, nada de banco vetorial novo. Monte o degrau um e o degrau três da escada, meça contra as suas cinquenta perguntas, e só então decida se alguma das outras ferramentas desta lista entra no seu problema.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h3&gt;Fontes&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://ai.google.dev/gemini-api/docs/pricing&quot;&gt;Google — Gemini API pricing&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://intuitionlabs.ai/articles/llm-api-pricing-cost-per-task-comparison&quot;&gt;IntuitionLabs — LLM API pricing 2026: comparação reproduzível de custo por tarefa&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.cloudzero.com/blog/llm-api-pricing/&quot;&gt;CloudZero — LLM API pricing 2026&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.spheron.network/blog/llm-api-pricing-comparison-gpt-claude-gemini-deepseek-2026/&quot;&gt;Spheron — comparação de preços e o ponto de virada do auto-hospedado&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://gloriumtech.com/?p=60079&quot;&gt;Glorium — casos de uso de IA em bancos e a classificação de crédito como alto risco no AI Act&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.sinequa.com/?p=6214&quot;&gt;Sinequa — IA agêntica em operações bancárias: crédito, lending e documentos&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://arxiv.org/abs/2106.09685&quot;&gt;Hu et al. (Microsoft, 2021) — LoRA: Low-Rank Adaptation of Large Language Models&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://arxiv.org/abs/2012.13255&quot;&gt;Aghajanyan et al. — Intrinsic Dimensionality Explains the Effectiveness of Language Model Fine-Tuning&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;em&gt;Dados de preço e referências regulatórias verificados em 22 de outubro de 2026.&lt;/em&gt;&lt;/p&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-1&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-1&quot; contenteditable=&quot;false&quot;&gt;1&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Modelo de linguagem (LLM).&lt;/strong&gt; Programa treinado para prever o próximo pedaço de texto a partir do anterior. Como um autocompletar que leu uma biblioteca inteira — e que, de tanto prever, aprendeu a raciocinar sobre o que prevê.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-2&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-2&quot; contenteditable=&quot;false&quot;&gt;2&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Pesos.&lt;/strong&gt; Os números internos do modelo, ajustados durante o treinamento. São o que o modelo &amp;quot;sabe&amp;quot; sem precisar que ninguém conte na hora.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-3&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-3&quot; contenteditable=&quot;false&quot;&gt;3&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Janela de contexto.&lt;/strong&gt; Quanto texto o modelo consegue ler de uma vez numa chamada. É a bancada de trabalho: tudo que não está em cima dela não existe para aquela tarefa.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-4&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-4&quot; contenteditable=&quot;false&quot;&gt;4&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Token.&lt;/strong&gt; O pedaço de texto que o modelo manipula — geralmente uma palavra curta ou parte de uma longa. É a unidade da conta, como o minuto era na ligação interurbana.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-5&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-5&quot; contenteditable=&quot;false&quot;&gt;5&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Tokenizador.&lt;/strong&gt; O componente que corta o texto em tokens. Cada modelo tem o seu, e o corte é mais eficiente nas línguas que estavam mais presentes no treino.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-6&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-6&quot; contenteditable=&quot;false&quot;&gt;6&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;RAG.&lt;/strong&gt; &lt;em&gt;Retrieval-Augmented Generation&lt;/em&gt;, geração aumentada por recuperação. Buscar o trecho certo antes de perguntar, como o advogado que consulta o artigo em vez de confiar na memória.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-7&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-7&quot; contenteditable=&quot;false&quot;&gt;7&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Embedding.&lt;/strong&gt; A representação de um texto como lista de números, construída de forma que textos de sentido parecido fiquem próximos. É o endereço do texto num mapa de significados.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-8&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-8&quot; contenteditable=&quot;false&quot;&gt;8&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Similaridade de cosseno.&lt;/strong&gt; Medida do ângulo entre dois vetores. Vale de -1 a 1; perto de 1 significa &amp;quot;apontam para o mesmo lado&amp;quot;, ou seja, falam da mesma coisa.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-9&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-9&quot; contenteditable=&quot;false&quot;&gt;9&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Alucinação.&lt;/strong&gt; Quando o modelo produz uma afirmação plausível e falsa. O problema não é o erro — é que ele vem com a mesma confiança do acerto.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-10&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-10&quot; contenteditable=&quot;false&quot;&gt;10&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Fine-tuning.&lt;/strong&gt; Treinar um modelo já pronto com os seus exemplos, para ajustar como ele responde. É o estágio na empresa depois da faculdade, não a faculdade de novo.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-11&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-11&quot; contenteditable=&quot;false&quot;&gt;11&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Gradiente / descida do gradiente.&lt;/strong&gt; O método que ajusta os pesos: mede o erro, calcula para que lado cada número deve se mover para errar menos, e dá um passo nessa direção. Milhões de vezes.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-12&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-12&quot; contenteditable=&quot;false&quot;&gt;12&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;LoRA.&lt;/strong&gt; &lt;em&gt;Low-Rank Adaptation&lt;/em&gt;. Em vez de reescrever a matriz de pesos, aprende um ajuste pequeno aplicado por cima — a errata grampeada no manual.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-13&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-13&quot; contenteditable=&quot;false&quot;&gt;13&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Adaptador.&lt;/strong&gt; O arquivo com o resultado de um treino LoRA. Pesa megabytes em vez de gigabytes e pode ser carregado e descarregado sobre o modelo base.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-14&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-14&quot; contenteditable=&quot;false&quot;&gt;14&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Chunking.&lt;/strong&gt; Quebrar documentos em pedaços indexáveis. O tamanho e o ponto do corte são a decisão que mais afeta a qualidade de um RAG.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-15&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-15&quot; contenteditable=&quot;false&quot;&gt;15&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Busca híbrida.&lt;/strong&gt; Combinar busca por significado com busca por palavra-chave, somando os resultados. Cobre o ponto cego da primeira, que são nomes, números e códigos.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-16&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-16&quot; contenteditable=&quot;false&quot;&gt;16&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Re-ranking.&lt;/strong&gt; Segunda passada que reordena os candidatos recuperados por relevância real, usando um modelo menor e mais criterioso que a busca vetorial.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-17&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-17&quot; contenteditable=&quot;false&quot;&gt;17&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Top-k.&lt;/strong&gt; Quantos trechos você recupera para colar no prompt. Mais nem sempre é melhor: trecho irrelevante no contexto atrapalha ativamente.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-18&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-18&quot; contenteditable=&quot;false&quot;&gt;18&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;pgvector.&lt;/strong&gt; Extensão do PostgreSQL que armazena vetores e faz busca por proximidade. Permite ter RAG sem adicionar um banco novo ao inventário.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-19&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-19&quot; contenteditable=&quot;false&quot;&gt;19&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;JSONL.&lt;/strong&gt; Arquivo de texto com um objeto JSON por linha. Formato padrão para conjuntos de treino porque é lido aos poucos, sem carregar tudo na memória.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-20&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-20&quot; contenteditable=&quot;false&quot;&gt;20&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Época.&lt;/strong&gt; Uma passada completa do treino por todos os exemplos. Poucas demais, o modelo não aprende; muitas, ele decora e perde a generalização.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-21&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-21&quot; contenteditable=&quot;false&quot;&gt;21&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Taxa de aprendizado.&lt;/strong&gt; O tamanho do passo a cada ajuste. Grande demais passa do ponto; pequena demais nunca chega.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-22&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-22&quot; contenteditable=&quot;false&quot;&gt;22&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Destilação.&lt;/strong&gt; Usar um modelo grande para ensinar um pequeno, gerando com ele os exemplos de treino. O especialista escreve o manual; o estagiário executa com o manual.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-23&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-23&quot; contenteditable=&quot;false&quot;&gt;23&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Cache de prompt.&lt;/strong&gt; Reaproveitamento cobrado uma vez só do trecho inicial do prompt que se repete entre chamadas. Exige que a parte fixa venha antes da variável.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-24&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-24&quot; contenteditable=&quot;false&quot;&gt;24&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Processamento em lote.&lt;/strong&gt; Enviar muitas requisições de uma vez aceitando resposta em horas em vez de segundos, em troca de desconto — tipicamente metade do preço.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-25&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-25&quot; contenteditable=&quot;false&quot;&gt;25&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;AI Act.&lt;/strong&gt; Regulamento europeu de inteligência artificial, que classifica aplicações por nível de risco e impõe obrigações proporcionais. Pontuação de crédito está na faixa de alto risco.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-26&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-26&quot; contenteditable=&quot;false&quot;&gt;26&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Memória de vídeo (VRAM).&lt;/strong&gt; A memória da placa de vídeo, onde o modelo precisa caber inteiro para treinar. É o gargalo real do treino — não o processador, não o disco.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-27&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-27&quot; contenteditable=&quot;false&quot;&gt;27&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Dimensão intrínseca.&lt;/strong&gt; O tamanho do espaço em que um modelo de fato se movimenta, bem menor que o número de parâmetros que ele tem. Como um mapa de mil ruas onde todo mundo só usa trinta.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-28&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-28&quot; contenteditable=&quot;false&quot;&gt;28&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Camadas adaptadoras.&lt;/strong&gt; Técnica anterior ao LoRA: blocos pequenos inseridos entre as camadas do modelo e treinados com o resto congelado. Economizam treino, mas custam latência em toda resposta.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-29&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-29&quot; contenteditable=&quot;false&quot;&gt;29&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;QLoRA.&lt;/strong&gt; LoRA com o modelo base congelado em precisão reduzida (4 bits). O modelo perde pouca qualidade e passa a caber numa GPU só — é o que torna o treino acessível fora de laboratório.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;div data-component-name=&quot;FootnoteToDOM&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; id=&quot;article-_m1zkKnleiYCgXKoZP4ImV9aY9g-footnote-30&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/rag-ou-fine-tuning-a-pergunta-certa#footnote-anchor-30&quot; contenteditable=&quot;false&quot;&gt;30&lt;/a&gt;&lt;div&gt;&lt;p&gt;&lt;strong&gt;Posto (rank).&lt;/strong&gt; Quantas direções realmente independentes existem dentro de uma tabela de números. Posto baixo significa que, apesar do tamanho, a tabela carrega pouca informação nova — e por isso pode ser comprimida sem perda relevante.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;</summary><author><name>Rafael Miguel</name></author><source gr:stream-id="feed/https://chroniclesofapragmaticprogrammer.substack.com/feed"><id>tag:google.com,2005:reader/feed/https://chroniclesofapragmaticprogrammer.substack.com/feed</id><title type="html">Chronicles of a Pragmatic Programmer</title><link rel="alternate" href="https://chroniclesofapragmaticprogrammer.substack.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792683298000"><id gr:original-id="https://thenewstack.io/?p=22841207">tag:google.com,2005:reader/item/00000b0e000000cd</id><category term="AI Engineering"></category><category term="Software Development"></category><category term="Software Testing"></category><title type="html">OpenAI’s COBOL migration score has made a striking leap</title><published>2026-10-22T15:34:58Z</published><updated>2026-10-22T15:34:58Z</updated><link rel="alternate" href="https://thenewstack.io/penai-cobol-migration-benchmark/" type="text/html"></link><summary type="html">OpenAI reports dramatic improvements in legacy software modernization, but it&amp;apos;s keeping the tests behind its biggest claim private.</summary><author><name>Meredith Shubel</name></author><source gr:stream-id="feed/https://thenewstack.io/software-testing/feed/"><id>tag:google.com,2005:reader/feed/https://thenewstack.io/software-testing/feed/</id><title type="html">Software Testing Archives - The New Stack</title><link rel="alternate" href="https://thenewstack.io/software-testing/" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792678970000"><id gr:original-id="https://thenewstack.io/?p=22841384">tag:google.com,2005:reader/item/00000b0e000000cc</id><category term="AI Models"></category><category term="Large Language Models"></category><category term="Software Testing"></category><title type="html">DeepSeek V4.1-Flash-1017 vs. Claude Haiku 5.5: More accurate at a fraction of the price</title><published>2026-10-22T14:22:50Z</published><updated>2026-10-22T14:22:50Z</updated><link rel="alternate" href="https://thenewstack.io/deepseek-flash-haiku-test/" type="text/html"></link><summary type="html">In our tests, DeepSeek’s updated open-weight Flash model beat Claude Haiku 5.5 on two tasks and tied on a third. Total cost: $0.07 versus Haiku’s $0.30.</summary><author><name>Jessica Wachtel</name></author><source gr:stream-id="feed/https://thenewstack.io/software-testing/feed/"><id>tag:google.com,2005:reader/feed/https://thenewstack.io/software-testing/feed/</id><title type="html">Software Testing Archives - The New Stack</title><link rel="alternate" href="https://thenewstack.io/software-testing/" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792656776000"><id gr:original-id="http://testwithnishi.com/?p=3032">tag:google.com,2005:reader/item/000005200000009d</id><category term="articles"></category><category term="Conferences &amp; Talks"></category><title type="html">That’s What She Said!</title><published>2026-10-22T08:12:56Z</published><updated>2026-10-22T08:12:56Z</updated><link rel="alternate" href="https://testwithnishi.com/2026/10/22/thats-what-she-said/" type="text/html"></link><summary type="html">What happens when you bring incredible women, honest conversations, and improv comedy into one room? ✨ A whole lot of connection, inspiration, and laughter! 🔅 Yesterday, I attended “That’s What She Said” at Boom Chicago in Amsterdam – and what a refreshing evening it was! It was so much more than networking. 🤝 💫 I […]&lt;p style=&quot;clear: both&quot;&gt;&lt;/p&gt;&lt;p data-bqr-info=&quot;attachment&quot;&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; src=&quot;https://testwithnishi.com/wp-content/uploads/2026/10/whatsapp-image-2026-10-22-at-9.40.46-am.jpeg&quot;&gt;&lt;/p&gt;</summary><author><name>Nishi Grover Garg</name></author><source gr:stream-id="feed/https://testwithnishi.com/feed/"><id>tag:google.com,2005:reader/feed/https://testwithnishi.com/feed/</id><title type="html">testwithnishi</title><link rel="alternate" href="https://testwithnishi.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792651607000"><id gr:original-id="https://medium.com/p/432179c042e6">tag:google.com,2005:reader/item/0000034700000085</id><title type="html">AI to Test Netflix Games</title><published>2026-10-22T06:46:47Z</published><updated>2026-10-22T06:46:47Z</updated><link rel="alternate" href="https://jarbon.medium.com/ai-to-test-netflix-games-carbon-for-games-432179c042e6?source=rss-1ee8fdc3bacb------2" type="text/html"></link><summary type="html">&lt;p&gt;&lt;em&gt;The AI controls the phone. The phone controls the game. And the test goes far beyond getting a high score.&lt;/em&gt;&lt;/p&gt;&lt;figure&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; alt src=&quot;https://cdn-images-1.medium.com/max/960/0*uky5xSPrXMNH7L8Y.gif&quot;&gt;&lt;/figure&gt;&lt;p&gt;&lt;em&gt;Recorded gameplay and the paired iPhone controller, with input indicators.&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;First, the AI learns the game.&lt;/h3&gt;&lt;p&gt;CARBON for Games starts with a learning phase. The AI explores the app, tries the controls, navigates menus and discovers available settings. It observes what happens after each action to learn how to start, play and recover when something goes wrong.&lt;/p&gt;&lt;p&gt;That knowledge becomes a &lt;strong&gt;test plan&lt;/strong&gt;: which player personas to exercise, which goals to pursue, which settings to vary and what outcomes to verify. Learning continues during testing. An unfamiliar screen or unexpected response can send the AI back into exploration. CARBON saves knowledge separately for each game and can revisit it as the app changes.&lt;/p&gt;&lt;h3&gt;One game. Two devices. A lot to coordinate.&lt;/h3&gt;&lt;p&gt;Some Netflix desktop games use a phone as the controller while the game runs in a browser. The AI must coordinate the phone, the browser and the connection between them. In this demo, it operates a physical iPhone paired with Tetris in the desktop browser.&lt;/p&gt;&lt;p&gt;Even getting to the first move takes work. Pairing links expire. A controller can appear connected but stop responding. A session may need a restart or cleared state. Setup, connection recovery and getting back into the game belong in the test plan too.&lt;/p&gt;&lt;h3&gt;The game will not wait for the AI.&lt;/h3&gt;&lt;p&gt;Gameplay is more demanding than clicking a predictable sequence of buttons. The AI has to understand the current state, choose a useful move, deliver the input and observe the result. In Tetris, a block keeps falling while that happens. A sensible decision delivered too late is still a bad move.&lt;/p&gt;&lt;p&gt;Our custom-trained gameplay model can run locally. In the recorded Max Score run, median model inference was &lt;strong&gt;1.77 milliseconds&lt;/strong&gt;. The complete observe-and-act loop was much slower: median placement time was &lt;strong&gt;4.56 seconds&lt;/strong&gt;, with phone input delivery a major bottleneck. Fast reasoning helps, but the whole interaction must keep up.&lt;/p&gt;&lt;p&gt;In our recorded Tetris demo, the result was &lt;strong&gt;1,260 points and six lines cleared&lt;/strong&gt; in a completed three-minute Time Attack round. This is a recorded run, not a promise of the same score every time. Capture gaps are marked in the video.&lt;/p&gt;&lt;h3&gt;Custom models, working together with AI.&lt;/h3&gt;&lt;p&gt;CARBON uses &lt;strong&gt;custom-trained gameplay models for different player personas&lt;/strong&gt;, working with AI components for other parts of the testing stack: exploration, test design, orchestration and evaluation. They interact through shared observations, plans, decisions and execution evidence to test apps together.&lt;/p&gt;&lt;p&gt;The current Tetris models learn from simulated games and outcome-based training. A selected persona guides gameplay decisions, while the broader AI workflow supplies context and evaluates what happened. The demo uses the local Max Score model; the exploration and evaluation components are separate AI capabilities, not all custom-trained models.&lt;/p&gt;&lt;p&gt;This division lets each part focus on its job: learn the app, create the test plan, choose actions, and verify execution. The useful output is a traceable result backed by what the system actually observed.&lt;/p&gt;&lt;h3&gt;Different players expose different problems.&lt;/h3&gt;&lt;p&gt;A perfect player can miss problems that a confused beginner finds immediately. CARBON’s testing workflow can explore multiple player styles:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;First timer:&lt;/strong&gt; discovers controls, misses instructions and makes understandable mistakes.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Casual player:&lt;/strong&gt; explores at a relaxed pace without optimizing every move.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;High scorer:&lt;/strong&gt; pursues points, combinations and efficient placements.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Speedrunner:&lt;/strong&gt; looks for quick routes and fewer inputs.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Rule breaker / cheater:&lt;/strong&gt; looks for loopholes in visible gameplay mechanics, such as unexpected action sequences or repeatable advantages.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Edge-case explorer:&lt;/strong&gt; probes boundaries, interruptions and unusual states.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The local Tetris bundle has trained policies for high score, casual, speedrunner and rule-breaker play. First-time discovery and wider edge-case exploration use the broader testing workflow. Each persona needs its own observed run before we can claim its tests passed.&lt;/p&gt;&lt;h3&gt;Personas describe how. Goals define what to test.&lt;/h3&gt;&lt;p&gt;A casual player can still test a difficult setting. A high scorer can test recovery after a pause. A test goal might be to complete a round, reach a score, discover every menu, exercise a shortcut or recover from a controller disconnect.&lt;/p&gt;&lt;p&gt;The plan also covers configuration. Does a difficulty change alter gameplay? Does the selected level appear correctly? Does music actually stop when it is turned off? Do settings persist when the player leaves a menu and returns?&lt;/p&gt;&lt;p&gt;Selecting an option is only the first half of that test. The AI must verify its effect using suitable evidence. A silent-looking screenshot cannot establish that music stopped; that check needs audio evidence. Missing evidence should remain an open question.&lt;/p&gt;&lt;h3&gt;The most interesting checks are qualitative.&lt;/h3&gt;&lt;p&gt;CARBON’s AI can examine more than scores, crashes and completed rounds. Did the player understand what happened? Was feedback timely and helpful? Did the controls feel responsive? Was a recovery path confusing? Did the game behave consistently with the player’s expectations?&lt;/p&gt;&lt;p&gt;These are observations to investigate, with evidence and context — not universal verdicts about what every player will enjoy. Different personas and goals make those questions more useful.&lt;/p&gt;&lt;h3&gt;Learn. Plan. Play. Verify. Learn more.&lt;/h3&gt;&lt;p&gt;CARBON for Games brings AI into the full testing workflow: learning the app, creating a test plan, executing scenarios with different personas and goals, checking outcomes, and reporting findings. New discoveries can feed back into the next round of learning and testing.&lt;/p&gt;&lt;p&gt;The result is a growing understanding of how the game works and where the player experience needs attention. Playing the game is how we get there.&lt;/p&gt;&lt;p&gt;Interested in AI game testing? Visit &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://testers.ai/&quot;&gt;Testers.ai&lt;/a&gt; or reply to this article. If someone on your team is testing games, share this with them.&lt;/p&gt;&lt;p&gt;This is an independent Testers.ai demonstration, not a Netflix endorsement, or collaboration.&lt;/p&gt;&lt;p&gt;— Jason Arbon, Testers.ai&lt;/p&gt;</summary><author><name>jason arbon</name></author><source gr:stream-id="feed/https://medium.com/feed/@jarbon"><id>tag:google.com,2005:reader/feed/https://medium.com/feed/@jarbon</id><title type="html">Stories by jason arbon on Medium</title><link rel="alternate" href="https://medium.com/@jarbon?source=rss-1ee8fdc3bacb------2" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792651607000"><id gr:original-id="https://medium.com/p/432179c042e6">tag:google.com,2005:reader/item/000009ff00000045</id><title type="html">AI to Test Netflix Games</title><published>2026-10-22T06:46:47Z</published><updated>2026-10-22T06:46:47Z</updated><link rel="alternate" href="https://jarbon.medium.com/ai-to-test-netflix-games-carbon-for-games-432179c042e6?source=rss-1ee8fdc3bacb------2" type="text/html"></link><summary type="html">&lt;p&gt;&lt;em&gt;The AI controls the phone. The phone controls the game. And the test goes far beyond getting a high score.&lt;/em&gt;&lt;/p&gt;&lt;figure&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; alt src=&quot;https://cdn-images-1.medium.com/max/960/0*uky5xSPrXMNH7L8Y.gif&quot;&gt;&lt;/figure&gt;&lt;p&gt;&lt;em&gt;Recorded gameplay and the paired iPhone controller, with input indicators.&lt;/em&gt;&lt;/p&gt;&lt;h3&gt;First, the AI learns the game.&lt;/h3&gt;&lt;p&gt;CARBON for Games starts with a learning phase. The AI explores the app, tries the controls, navigates menus and discovers available settings. It observes what happens after each action to learn how to start, play and recover when something goes wrong.&lt;/p&gt;&lt;p&gt;That knowledge becomes a &lt;strong&gt;test plan&lt;/strong&gt;: which player personas to exercise, which goals to pursue, which settings to vary and what outcomes to verify. Learning continues during testing. An unfamiliar screen or unexpected response can send the AI back into exploration. CARBON saves knowledge separately for each game and can revisit it as the app changes.&lt;/p&gt;&lt;h3&gt;One game. Two devices. A lot to coordinate.&lt;/h3&gt;&lt;p&gt;Some Netflix desktop games use a phone as the controller while the game runs in a browser. The AI must coordinate the phone, the browser and the connection between them. In this demo, it operates a physical iPhone paired with Tetris in the desktop browser.&lt;/p&gt;&lt;p&gt;Even getting to the first move takes work. Pairing links expire. A controller can appear connected but stop responding. A session may need a restart or cleared state. Setup, connection recovery and getting back into the game belong in the test plan too.&lt;/p&gt;&lt;h3&gt;The game will not wait for the AI.&lt;/h3&gt;&lt;p&gt;Gameplay is more demanding than clicking a predictable sequence of buttons. The AI has to understand the current state, choose a useful move, deliver the input and observe the result. In Tetris, a block keeps falling while that happens. A sensible decision delivered too late is still a bad move.&lt;/p&gt;&lt;p&gt;Our custom-trained gameplay model can run locally. In the recorded Max Score run, median model inference was &lt;strong&gt;1.77 milliseconds&lt;/strong&gt;. The complete observe-and-act loop was much slower: median placement time was &lt;strong&gt;4.56 seconds&lt;/strong&gt;, with phone input delivery a major bottleneck. Fast reasoning helps, but the whole interaction must keep up.&lt;/p&gt;&lt;p&gt;In our recorded Tetris demo, the result was &lt;strong&gt;1,260 points and six lines cleared&lt;/strong&gt; in a completed three-minute Time Attack round. This is a recorded run, not a promise of the same score every time. Capture gaps are marked in the video.&lt;/p&gt;&lt;h3&gt;Custom models, working together with AI.&lt;/h3&gt;&lt;p&gt;CARBON uses &lt;strong&gt;custom-trained gameplay models for different player personas&lt;/strong&gt;, working with AI components for other parts of the testing stack: exploration, test design, orchestration and evaluation. They interact through shared observations, plans, decisions and execution evidence to test apps together.&lt;/p&gt;&lt;p&gt;The current Tetris models learn from simulated games and outcome-based training. A selected persona guides gameplay decisions, while the broader AI workflow supplies context and evaluates what happened. The demo uses the local Max Score model; the exploration and evaluation components are separate AI capabilities, not all custom-trained models.&lt;/p&gt;&lt;p&gt;This division lets each part focus on its job: learn the app, create the test plan, choose actions, and verify execution. The useful output is a traceable result backed by what the system actually observed.&lt;/p&gt;&lt;h3&gt;Different players expose different problems.&lt;/h3&gt;&lt;p&gt;A perfect player can miss problems that a confused beginner finds immediately. CARBON’s testing workflow can explore multiple player styles:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;First timer:&lt;/strong&gt; discovers controls, misses instructions and makes understandable mistakes.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Casual player:&lt;/strong&gt; explores at a relaxed pace without optimizing every move.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;High scorer:&lt;/strong&gt; pursues points, combinations and efficient placements.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Speedrunner:&lt;/strong&gt; looks for quick routes and fewer inputs.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Rule breaker / cheater:&lt;/strong&gt; looks for loopholes in visible gameplay mechanics, such as unexpected action sequences or repeatable advantages.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Edge-case explorer:&lt;/strong&gt; probes boundaries, interruptions and unusual states.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;The local Tetris bundle has trained policies for high score, casual, speedrunner and rule-breaker play. First-time discovery and wider edge-case exploration use the broader testing workflow. Each persona needs its own observed run before we can claim its tests passed.&lt;/p&gt;&lt;h3&gt;Personas describe how. Goals define what to test.&lt;/h3&gt;&lt;p&gt;A casual player can still test a difficult setting. A high scorer can test recovery after a pause. A test goal might be to complete a round, reach a score, discover every menu, exercise a shortcut or recover from a controller disconnect.&lt;/p&gt;&lt;p&gt;The plan also covers configuration. Does a difficulty change alter gameplay? Does the selected level appear correctly? Does music actually stop when it is turned off? Do settings persist when the player leaves a menu and returns?&lt;/p&gt;&lt;p&gt;Selecting an option is only the first half of that test. The AI must verify its effect using suitable evidence. A silent-looking screenshot cannot establish that music stopped; that check needs audio evidence. Missing evidence should remain an open question.&lt;/p&gt;&lt;h3&gt;The most interesting checks are qualitative.&lt;/h3&gt;&lt;p&gt;CARBON’s AI can examine more than scores, crashes and completed rounds. Did the player understand what happened? Was feedback timely and helpful? Did the controls feel responsive? Was a recovery path confusing? Did the game behave consistently with the player’s expectations?&lt;/p&gt;&lt;p&gt;These are observations to investigate, with evidence and context — not universal verdicts about what every player will enjoy. Different personas and goals make those questions more useful.&lt;/p&gt;&lt;h3&gt;Learn. Plan. Play. Verify. Learn more.&lt;/h3&gt;&lt;p&gt;CARBON for Games brings AI into the full testing workflow: learning the app, creating a test plan, executing scenarios with different personas and goals, checking outcomes, and reporting findings. New discoveries can feed back into the next round of learning and testing.&lt;/p&gt;&lt;p&gt;The result is a growing understanding of how the game works and where the player experience needs attention. Playing the game is how we get there.&lt;/p&gt;&lt;p&gt;Interested in AI game testing? Visit &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://testers.ai/&quot;&gt;Testers.ai&lt;/a&gt; or reply to this article. If someone on your team is testing games, share this with them.&lt;/p&gt;&lt;p&gt;This is an independent Testers.ai demonstration, not a Netflix endorsement, or collaboration.&lt;/p&gt;&lt;p&gt;— Jason Arbon, Testers.ai&lt;/p&gt;</summary><author><name>jason arbon</name></author><source gr:stream-id="feed/https://jarbon.medium.com/feed"><id>tag:google.com,2005:reader/feed/https://jarbon.medium.com/feed</id><title type="html">Stories by jason arbon on Medium</title><link rel="alternate" href="https://medium.com/@jarbon?source=rss-1ee8fdc3bacb------2" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792649700000"><id gr:original-id="https://karlosmid.com/2026/10/my-learning-bits-for-thursday-22nd-october-2026">tag:google.com,2005:reader/item/00000563000001e3</id><category term="learning"></category><category term="fortran"></category><category term="llms"></category><category term="programming"></category><title type="html">My learning bits for Thursday of 22nd October 2026</title><published>2026-10-22T06:15:00Z</published><updated>2026-10-22T06:15:00Z</updated><link rel="alternate" href="https://karlosmid.com/2026/10/my-learning-bits-for-thursday-22nd-october-2026/" type="text/html"></link><summary type="html">&lt;p&gt;From Fortran to LLMs, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://hrih.si/articles/the-compiler-was-supposed-to-end-programming/&quot; rel=&quot;noopener noreferrer&quot;&gt;https://hrih.si/articles/the-compiler-was-supposed-to-end-programming/&lt;/a&gt;&lt;/p&gt;</summary><author><name></name></author><source gr:stream-id="feed/https://blog.tentamen.eu/feed/"><id>tag:google.com,2005:reader/feed/https://blog.tentamen.eu/feed/</id><title type="html">Tentamen Software Testing Blog</title><link rel="alternate" href="https://karlosmid.com/" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792635435000"><id gr:original-id="https://pricillabilavendran.com/?p=2977">tag:google.com,2005:reader/item/00000d9300000017</id><category term="AI"></category><category term="Community"></category><category term="Experience"></category><category term="General"></category><title type="html">A Letter to a Developer, From a Tester 💙</title><published>2026-10-22T02:17:15Z</published><updated>2026-10-22T02:17:15Z</updated><link rel="alternate" href="https://pricillabilavendran.com/2026/10/22/a-letter-to-a-developer-from-a-tester/" type="text/html"></link><summary type="html">&lt;p&gt;We’ve all heard the joke, haven’t we?&lt;/p&gt;
&lt;p&gt;Developers and Testers are like Tom and Jerry. One writes the code, and the other tries to find something wrong with it. One fixes a bug, and the other comes back with another one. And the chase continues!&lt;/p&gt;
&lt;p&gt;It’s a familiar stereotype, and perhaps we all have laughed at it at some point.&lt;/p&gt;
&lt;p&gt;But I think our story has evolved.&lt;/p&gt;
&lt;p&gt;Over the years, I have had the privilege of working with some wonderful developers. Some challenged me with a healthy competitive spirit. Some patiently explained their code when I wanted to understand what was happening behind the scenes. Others helped me understand build releases and the technical decisions that shaped the product.&lt;/p&gt;
&lt;p&gt;These experiences meant more to me than they might have realised.&lt;/p&gt;
&lt;p&gt;They reminded me that collaboration isn’t just about attending the same meetings or working towards the same deadline. It’s about sharing knowledge, listening to another perspective, and being willing to learn from each other.&lt;/p&gt;
&lt;p&gt;And I’m happy to see more teams embracing this mindset. Developers and testers are collaborating earlier, discussing risks together, and treating quality as a shared responsibility rather than something that belongs to just one role.&lt;/p&gt;
&lt;p&gt;There is still room to grow, of course, but I think we should appreciate how far we have come.&lt;/p&gt;
&lt;p&gt;And that’s why I want to write this letter.&lt;/p&gt;
&lt;h2&gt;Dear Developer,&lt;/h2&gt;
&lt;p&gt;I know how much thought, creativity, and effort go into building software. Behind a feature that looks simple to a user, there may be hundreds of decisions, complicated logic, integrations, and hours of problem-solving.&lt;/p&gt;
&lt;p&gt;As a tester, I may come along with questions, unexpected scenarios, or a defect report. But my intention isn’t to question your ability or make your work harder.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m not here to find bugs just to bug you.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’m here because I care about the product, too.&lt;/p&gt;
&lt;p&gt;I want to understand what you’re building. I want to ask questions that might uncover a hidden assumption. I want to explore what happens when a user does something unexpected, when two systems interact, or when a perfectly reasonable feature behaves differently in the real world.&lt;/p&gt;
&lt;p&gt;Sometimes, I’ll find a defect. Sometimes, I’ll raise a concern that turns out not to be a problem. And sometimes, you’ll explain something that helps me understand the system in a completely new way.&lt;/p&gt;
&lt;p&gt;That’s the beauty of working together.&lt;/p&gt;
&lt;p&gt;Some of my best learning experiences have come from developers who were willing to explain &lt;em&gt;why&lt;/em&gt; something worked the way it did, rather than simply telling me that it worked as expected.&lt;/p&gt;
&lt;p&gt;Those conversations helped me ask better questions, understand technical risks, and become a better tester. I hope my questions and observations have helped you see your work from a different perspective, too.&lt;/p&gt;
&lt;h2&gt;Dear Developer, There Are Things I Admire About You&lt;/h2&gt;
&lt;p&gt;There are so many things I admire about what you do, and perhaps I don’t say them often enough.&lt;/p&gt;
&lt;p&gt;I admire how you navigate hundreds, sometimes thousands, of lines of code, understanding how each piece connects to another. What might look like a maze to someone else is a system you have patiently built, understood, and maintained.&lt;/p&gt;
&lt;p&gt;I admire how you investigate a defect, trace it back through layers of logic, and work to understand what went wrong.&lt;/p&gt;
&lt;p&gt;And when we finally get to the bottom of it, I appreciate those moments when you look at the evidence and say, &lt;em&gt;“Yes, that’s a bug.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Not because I was right and you were wrong, but because &lt;strong&gt;we found something that could affect someone using our product.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That willingness to listen, investigate, and acknowledge an issue is something I deeply respect.&lt;/p&gt;
&lt;p&gt;I admire how you explore new technologies, discuss technical possibilities, and think through solutions that many of us might never have considered. I admire how you translate complex technical decisions into language the business can understand, helping people see not just what is possible, but what makes sense for the product.&lt;/p&gt;
&lt;p&gt;And I admire the creativity behind your work. The time spent untangling a complicated problem, the careful decisions that make a system more reliable, and the satisfaction of finally getting something to work.&lt;/p&gt;
&lt;p&gt;Sometimes, as testers, we see the outcome of your work through test cases, failures, and defect reports. But behind every line of code is thought, effort, and a person trying to solve a problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I see that effort. I respect it. And I appreciate it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To the developers who have patiently answered my questions, walked me through technical details, and helped me understand the bigger picture, thank you. Those moments have shaped the way I work, and I hope I’ve made a positive difference to your work, too.&lt;/p&gt;
&lt;p&gt;We may have different strengths, but that is precisely what makes our collaboration valuable.&lt;/p&gt;
&lt;h2&gt;Different Perspectives, One Shared Goal&lt;/h2&gt;
&lt;p&gt;One of the things I appreciate most about working with developers is that we don’t always look at a problem in the same way.&lt;/p&gt;
&lt;p&gt;You might look at a feature and think about its architecture, implementation, performance, and maintainability. I’ll look at it and wonder what happens when the user takes an unexpected path, when the data is incomplete, or when something goes wrong at the worst possible moment.&lt;/p&gt;
&lt;p&gt;Neither perspective is more important than the other. They complement each other.&lt;/p&gt;
&lt;p&gt;Sometimes, a question from a tester leads to a better design. Sometimes, a developer’s explanation helps a tester identify a risk they hadn’t considered. Sometimes, a healthy technical debate leads us to a solution that neither of us would have reached alone.&lt;/p&gt;
&lt;p&gt;And sometimes, we simply learn something new from each other.&lt;/p&gt;
&lt;p&gt;That’s not conflict. That’s collaboration doing what it does best.&lt;/p&gt;
&lt;p&gt;We won’t always agree, and we don’t have to. What matters is that we can challenge ideas while respecting the people behind them.&lt;/p&gt;
&lt;p&gt;When a defect is raised, we can investigate it together. When a technical constraint exists, we can explore the options together. When something goes wrong, we can focus on understanding the problem rather than finding someone to blame.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The goal isn’t to prove who’s right. The goal is to get it right.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And when we do, the real winner is the end user using our software/product.&lt;/p&gt;
&lt;h2&gt;And Now, in the Age of AI…&lt;/h2&gt;
&lt;p&gt;Our tools are changing, and so is the way we build and test software.&lt;/p&gt;
&lt;p&gt;AI can help developers generate code, explore solutions, and explain complex logic. It can help testers create test cases, analyse failures, and discover scenarios worth investigating.&lt;/p&gt;
&lt;p&gt;These capabilities give us even more opportunities to learn from each other and improve the way we work.&lt;/p&gt;
&lt;p&gt;But faster work doesn’t automatically mean better software.&lt;/p&gt;
&lt;p&gt;We still need to ask questions, challenge assumptions, understand context, and consider how real people will experience the product. We need to think critically about what our tools produce and work together to determine whether the results are good enough.&lt;/p&gt;
&lt;p&gt;I see this as another opportunity for developers and testers to strengthen their partnership.&lt;/p&gt;
&lt;p&gt;We don’t need to compete over who can produce more code or find more bugs. We can use these tools to spend more time understanding risks, discussing solutions, and making thoughtful decisions about quality.&lt;/p&gt;
&lt;p&gt;Our tools will continue to evolve, and our roles will evolve with them. But our shared responsibility towards the people who trust our software remains.&lt;/p&gt;
&lt;h2&gt;A Little Appreciation Goes a Long Way&lt;/h2&gt;
&lt;p&gt;To the developers who have shared their knowledge with me, answered my questions, explained the complicated bits, challenged my thinking, and treated testing as a collaborative effort: thank you.&lt;/p&gt;
&lt;p&gt;You may not remember every conversation, but I remember how those moments helped me grow.&lt;/p&gt;
&lt;p&gt;And to every developer reading this, I hope you know that your work is seen and appreciated.&lt;/p&gt;
&lt;p&gt;When I raise a defect, I’m not standing on the opposite side of the table. I’m standing beside you, looking at the same product, trying to make it better.&lt;/p&gt;
&lt;p&gt;You bring your creativity and engineering expertise. I bring my curiosity, testing skills, and a different perspective. Together, we bring a more complete understanding of the product and the people who will use it.&lt;/p&gt;
&lt;p&gt;So, perhaps it’s time we gave Tom and Jerry a little break!&lt;/p&gt;
&lt;p&gt;Let’s celebrate the partnerships that are already making a difference. Let’s keep sharing knowledge, asking questions, challenging assumptions, and learning from each other.&lt;/p&gt;
&lt;p&gt;Because great software isn’t built by developers alone or made reliable by testers alone. It’s built by people who care about what they’re creating and work together to make it better.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dear Developer, I am glad we are on the same team.&lt;/strong&gt; &lt;img class=&quot;bqrUnknownImgSize&quot; src=&quot;https://s.w.org/images/core/emoji/17.0.2/72x72/1f499.png&quot; alt=&quot;💙&quot; style=&quot;height: 1em&quot;&gt;&lt;/p&gt;
&lt;p&gt;Let’s keep building software worth trusting, together.&lt;/p&gt;</summary><author><name>Pricilla Bilavendran</name></author><source gr:stream-id="feed/https://pricillabilavendran.com/feed/"><id>tag:google.com,2005:reader/feed/https://pricillabilavendran.com/feed/</id><title type="html">The Journal of a Tester: Blog about my survival escapades 🚀</title><link rel="alternate" href="https://pricillabilavendran.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792620704000"><id gr:original-id="https://qualityremarks.com/?p=7653">tag:google.com,2005:reader/item/00000463000004db</id><category term="Software Testing"></category><title type="html">Main 3k Character Syndrome</title><published>2026-10-21T22:11:44Z</published><updated>2026-10-21T22:11:44Z</updated><link rel="alternate" href="https://qualityremarks.com/main-3k-character-syndrome/" type="text/html"></link><summary type="html">&lt;p&gt;There are reasons this timeline is so exhausting.&lt;/p&gt;



&lt;p&gt;Between all the manufactured urgency of forced AI adoption, constantly moving goal posts around retraining, and relentless fear inducing job insecurity, AI related anxiety and burnout are taking a &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.sciencedirect.com/science/article/abs/pii/S0160791X25002428&quot; rel=&quot;noopener&quot;&gt;heavy toll&lt;/a&gt; on the &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.apa.org/pubs/reports/work-in-america/2026&quot; rel=&quot;noopener&quot;&gt;mental health&lt;/a&gt; of tech workers and the general public. &lt;/p&gt;



&lt;span id=&quot;article-nrocjqe5_KJOOKfvtZcHypqdkNY-more-7653&quot;&gt;&lt;/span&gt;



&lt;p&gt;A &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.apa.org/pubs/reports/work-in-america/2026&quot; rel=&quot;noopener&quot;&gt;report&lt;/a&gt; from the America Psychological Association is revealing, with workers reporting that “technological change is benefiting employers more than workers.” The numbers are also increasing this year for folks worrying that they will “fall behind other workers at their company if they do not use AI”.&lt;/p&gt;



&lt;p&gt;So to all my dear friends in the AI cargo cult of middle management perpetuating this pyramid scheme of abuse, I’m sure talking to a positive affirmation machine all day might give you a touch of main character syndrome, but good grief, don’t assume everything is about you!&lt;/p&gt;



&lt;p&gt;A &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.fastcompany.com/91576086/ai-psychosis-is-the-new-leadership-blind-spot-ai-leadership-blind-spots&quot; rel=&quot;noopener&quot; title&gt;piece&lt;/a&gt; in &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.fastcompany.com/&quot; rel=&quot;noopener&quot;&gt;Fast Company&lt;/a&gt; “AI psychosis is the new leadership blind spot” nails why that’s so hard when “polished-looking output with less substance beneath”, that the folks at Stanford and BetterUp call “workslop” gets to leadership. Who isn’t feeling this lately? The amount of incompetent experts AI is churning out might be the reason their talk is converging on the same ideas as well. &lt;/p&gt;



&lt;p&gt;Skepticism is essential to responsible tool adoption and not the tired, boring claim of protecting the old guard, but just like that scene in &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://en.wikipedia.org/wiki/Her_(2013_film)&quot; rel=&quot;noopener&quot;&gt;Her&lt;/a&gt; when he finds out his AI girlfriend is simultaneously talking to thousands of other people and “in love” with a bunch of them at the same time, guess what:&lt;/p&gt;



&lt;p&gt;“In one recent survey, 74% of executives said they had more confidence in AI’s advice than in that of colleagues or friends, and 44% said they would defer to its reasoning over their own insights.” &lt;/p&gt;



&lt;p&gt;So if you feel like you’re taking crazy pills, remember it’s definitely not you – it’s them. We’re under a multi-billion dollar marketing assault that has “&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://sensortower.com/blog/state-of-ai-2026&quot; rel=&quot;noopener&quot;&gt;more than tripled in the US in Q1 2026, with OpenAI and Anthropic increasing spend by 800%+ YoY as competition for users intensifies.&lt;/a&gt;” Nothing about this is normal.&lt;/p&gt;



&lt;p&gt;I know I’m feeling on the edge of burn out these days, so I guess it’s also time for our regular reminder to those being called a Luddite for being skeptical of AI bros or vendor claims that:&lt;/p&gt;



&lt;ul&gt;
&lt;li&gt;Thinking critically about a technology is NOT being AGAINST it…&lt;/li&gt;



&lt;li&gt;Considering harm in a technology’s use is NOT being AGAINST it…&lt;/li&gt;



&lt;li&gt;Being critical of the marketing of a technology is NOT being AGAINST it…&lt;/li&gt;



&lt;li&gt;Having principles and values drive your adoption of technology is NOT being AGAINST it…&lt;/li&gt;



&lt;li&gt;Calling out unverifiable claims used misleadingly to sell a technology is NOT being AGAINST it…&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;But as my friend &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://developsense.com/&quot; rel=&quot;noopener&quot;&gt;Michael Bolton&lt;/a&gt; likes to say, “Good luck with your thing”…&lt;/p&gt;The post &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://qualityremarks.com/main-3k-character-syndrome/&quot;&gt;Main 3k Character Syndrome&lt;/a&gt; first appeared on &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://qualityremarks.com&quot;&gt;Quality Remarks&lt;/a&gt;.</summary><author><name>keithklain</name></author><source gr:stream-id="feed/http://qualityremarks.com/feed/"><id>tag:google.com,2005:reader/feed/http://qualityremarks.com/feed/</id><title type="html">Quality Remarks</title><link rel="alternate" href="https://qualityremarks.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792617143000"><id gr:original-id="https://testerstories.com/?p=6918">tag:google.com,2005:reader/item/000001ce000000b5</id><category term="AI"></category><category term="Game Testing"></category><title type="html">Testing Adventure</title><published>2026-10-21T21:12:23Z</published><updated>2026-10-21T21:12:23Z</updated><link rel="alternate" href="https://testerstories.com/2026/10/testing-adventure/" type="text/html"></link><summary type="html">&lt;p&gt;Much like my &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://testerstories.com/2025/01/test-defender/&quot;&gt;Test Defender&lt;/a&gt; post, this post’s title is a bit of a double entendre. It’s (hopefully) going to be an adventure but it’s also testing &lt;em&gt;Adventure&lt;/em&gt;. And, yes, there will be something about AI in all this.&lt;/p&gt;
&lt;p&gt;&lt;span id=&quot;article-bCfb6txf_qe2e-_PsRByEx5ZvZI-more-6918&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; decoding=&quot;async&quot; src=&quot;https://testerstories.com/files/BLOG-adventure-uncartridged-title.png&quot; style=&quot;border-radius: 20%&quot;&gt;&lt;/p&gt;
&lt;p&gt;So, imagine a time when home video games were mostly simple arcade ports (like &lt;em&gt;Pong&lt;/em&gt; or &lt;em&gt;Space Invaders&lt;/em&gt;) where players just shot at targets or chased a high score on a single screen. Yep, you guessed it. I’m talking about the Dark Ages as the 1970s slipped into the 1980s. In this context, a game called &lt;em&gt;Adventure&lt;/em&gt; was the first action-adventure game on a console. Specifically, on the Atari 2600. It allowed players to explore a vast (well, vast for the time) world spanning multiple screens, carry items in an inventory (or, rather, attached to your body), and search for a legendary artifact. It was almost exciting.&lt;/p&gt;
&lt;p&gt;Because I clearly know how to use my time wisely, I decided to create a modern version of this. Well, modern insofar as it’s an exact recreation, but playable in a browser. So, retro-modern? Modern-retro? I have no idea. It’s called &lt;em&gt;Adventure Uncartridged&lt;/em&gt;. It’s &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged&quot;&gt;open sourced&lt;/a&gt; so you can make fun of it. And it’s &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://jeffnyman.github.io/adventure-uncartridged/&quot;&gt;directly playable online&lt;/a&gt; so you at least know what you’re making fun of.&lt;/p&gt;
&lt;p&gt;If you’re thinking, “Wow, that looks hideous!”, well, don’t blame me. Blame the 1980s. You know, retro.&lt;/p&gt;
&lt;p&gt;For purposes of this blog post, I want to talk about testing this game. Don’t worry! I already covered &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://testerstories.com/2021/10/testing-like-it-was-1980/&quot;&gt;testing like it was 1980&lt;/a&gt;. We’re not quite going to do that.&lt;/p&gt;
&lt;p&gt;If you are someone who is a tester or who has to think like a tester, and thus adopt the testing role, it’s worth looking at this game and considering how you would test it. Awhile back I wrote a &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://testerstories.com/2014/12/the-quest-for-testers/&quot;&gt;Test Quest&lt;/a&gt; game that I used for interviews, because I found then, and still do today, that you can learn a whole lot from watching someone decide how to test a game; moreso than any other application.&lt;/p&gt;
&lt;h2&gt;Purpose and Audience&lt;/h2&gt;
&lt;p&gt;So let’s keep in mind some crucial context here: this is a port of the original game, staying as true to the original as possible. (That’s for better &lt;em&gt;and&lt;/em&gt; worse, mind you.) One goal of testing this game is to find every place where the browser version behaves differently from the 1979 Atari 2600 original. Playability and a stable build matter too, but fidelity comes first.&lt;/p&gt;
&lt;p&gt;Wait, playability is secondary? It would seem like a primary quality attribute, would it not? Well, sure, it is, but remember: if fidelity is the goal, then how playable or not it is would remain a function of what the game was. That’s important to keep in mind. Unless they have a time machine and don’t mind altering the past, any bugs a tester wrote about pure playability would be entirely missing the point.&lt;/p&gt;
&lt;p&gt;This strategy serves two kinds of test thinking, and most sections I talk about here are marked for one or both.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Testers who explore by playing. The live game, the manual, an emulator; using keyboard, notes, screen recordings, and a healthy dose of nostalgia.&lt;/li&gt;
&lt;li&gt;Testers who explore the code. The TypeScript and (maybe?) 6502 assembly; using the source, the tests, the original assembly listing; involving code review, unit and property tests, and scripted play.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The two tracks feed each other. An exploratory tester can see a dragon behave oddly but can also find the line responsible. A tester who spots suspicious code can also use that finding to try a very precise thing in the game.&lt;/p&gt;
&lt;h2&gt;What Correct Means&lt;/h2&gt;
&lt;p&gt;In this context, the original game is the specification. When my version and the original disagree, the default verdict is that my version has a bug. That makes the usual question of how to know what correct looks like unusually answerable.&lt;/p&gt;
&lt;p&gt;You can the following sources, in order of authority. And this is what I used as I was creating this and testing this port.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The original game running in an emulator. The Stella emulator plays the original cartridge image, which ships in my repository at &lt;em&gt;docs/adventure.bin&lt;/em&gt;. You could play the same scenario side by side and compare. This is the main oracle for testers. It certainly was for me.&lt;/li&gt;
&lt;li&gt;The annotated assembly listing. &lt;em&gt;docs/adventure.asm&lt;/em&gt; is the original source with comments. Many functions in &lt;em&gt;src/adventure.ts&lt;/em&gt; mirror a named routine in the assembly. This is a secondary, but still important, oracle for testers.&lt;/li&gt;
&lt;li&gt;The original manual. &lt;em&gt;docs/adventure-manual.pdf&lt;/em&gt; describes intended play, but not edge cases.&lt;/li&gt;
&lt;li&gt;Project notes. You, as a reader, have &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/main/docs/DIFFICULTY.md&quot;&gt;&lt;em&gt;docs/DIFFICULTY.md&lt;/em&gt;&lt;/a&gt; and &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/main/docs/ASSEMBLY.md&quot;&gt;&lt;em&gt;docs/ASSEMBLY.md&lt;/em&gt;&lt;/a&gt;, which explain how specific mechanics work. I didn’t have those, of course, since I had to write them!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Some differences are deliberate, because a browser is not an Atari 2600. The keyboard replaces the joystick, buttons replace the difficulty switches, and keys 1 and 2 stand in for the console’s Reset and Select switches. A tester who finds a difference should first ask whether it’s one of these. Any other deliberate deviation should, of course, be questioned.&lt;/p&gt;
&lt;p&gt;It is defensible to ask whether the game should support a joystick even in browser form, but it’s not defensible to say the browser should use the same exact mechanics as those that existed on a physical Atari 2600.&lt;/p&gt;
&lt;h2&gt;Risk Areas&lt;/h2&gt;
&lt;p&gt;As in all things, testing effort should follow risk, and the highest risk is a silent translation error from 6502 assembly into TypeScript. This would be a case where the game still runs, but some behavior is subtly wrong. When I was testing, I wrote up the following table that ranks the risk areas this strategy targets.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;Rank&lt;/td&gt;
&lt;td&gt;Risk&lt;/td&gt;
&lt;td&gt;Why it is risky here&lt;/td&gt;
&lt;td&gt;Tracks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Translation errors&lt;/td&gt;
&lt;td&gt;The original relies on 8-bit wraparound, bit order and unsigned math that TypeScript does not copy automatically&lt;/td&gt;
&lt;td&gt;Technical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Randomness&lt;/td&gt;
&lt;td&gt;Game 3 placement and the bat depend on random numbers, so bugs hide until the right roll&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Frame timing&lt;/td&gt;
&lt;td&gt;The 2600 ran at a fixed rate. A browser runs at the display’s refresh rate, and tabs can be paused&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;State interactions&lt;/td&gt;
&lt;td&gt;Carrying, being eaten, the bat, the magnet and portals can all happen on the same frame&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Room connections&lt;/td&gt;
&lt;td&gt;Exits change with the game level, so a wrong table entry only shows at one level&lt;/td&gt;
&lt;td&gt;Human&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Input handling&lt;/td&gt;
&lt;td&gt;A keyboard is not a joystick. Keys can stick, overlap or be released while the tab is unfocused&lt;/td&gt;
&lt;td&gt;Human&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Toolchain drift&lt;/td&gt;
&lt;td&gt;Dependency updates can change the formatter, test runner or build without any code change&lt;/td&gt;
&lt;td&gt;Technical&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;The Game 3 freeze, covered in the case study below, was a rank 2 bug with a rank 1 cause.&lt;/p&gt;
&lt;h2&gt;Test Layers&lt;/h2&gt;
&lt;p&gt;The &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/tree/main/tests&quot;&gt;automated tests&lt;/a&gt; cover the pure geometry fairly well. Even given that coverage, everything that depends on game state, randomness or input is still tested only by playing. The layers below go from cheapest to most expensive.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;Layer&lt;/td&gt;
&lt;td&gt;What it checks&lt;/td&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;Track&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unit tests&lt;/td&gt;
&lt;td&gt;Collision, playfield bit mapping, room picks&lt;/td&gt;
&lt;td&gt;In place&lt;/td&gt;
&lt;td&gt;Technical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Property tests&lt;/td&gt;
&lt;td&gt;The same functions across thousands of generated inputs&lt;/td&gt;
&lt;td&gt;In place&lt;/td&gt;
&lt;td&gt;Technical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State machine tests&lt;/td&gt;
&lt;td&gt;Every transition below, driven by scripted switch presses&lt;/td&gt;
&lt;td&gt;Not started&lt;/td&gt;
&lt;td&gt;Technical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scripted playthroughs&lt;/td&gt;
&lt;td&gt;A recorded input sequence with fixed random numbers, checked against the expected end state&lt;/td&gt;
&lt;td&gt;Not started (although see agent section below)&lt;/td&gt;
&lt;td&gt;Technical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual comparison&lt;/td&gt;
&lt;td&gt;Each room rendered and compared with an emulator screenshot&lt;/td&gt;
&lt;td&gt;Not started&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exploratory sessions&lt;/td&gt;
&lt;td&gt;Time-boxed play against a charter&lt;/td&gt;
&lt;td&gt;Ongoing&lt;/td&gt;
&lt;td&gt;Human&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; decoding=&quot;async&quot; src=&quot;https://testerstories.com/files/BLOG-adventure-001.png&quot; style=&quot;border-radius: 20%&quot;&gt;&lt;/p&gt;
&lt;p&gt;A test I found it was very easy to miss was that a mid-game reset puts the player back at the castle and revives all the dragons, but leaves objects where they are. That’s just how the game worked. Because transitions fire on release, both test types should hold keys down across transitions and release them at awkward moments.&lt;/p&gt;
&lt;h2&gt;Testability&lt;/h2&gt;
&lt;p&gt;In the developer role, I worked hard to make sure that the code already had the right seam for deeper testing. Every contact with the browser goes through &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/main/src/hardware.ts&quot;&gt;&lt;em&gt;src/hardware.ts&lt;/em&gt;&lt;/a&gt;, and the game itself advances one frame per call to &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/main/src/adventure.ts#L77&quot;&gt;&lt;em&gt;startGame&lt;/em&gt;&lt;/a&gt;. So why are three of those layers still marked “Not started”?&lt;/p&gt;
&lt;p&gt;It’s not because I didn’t think of them. (Of course, I &lt;em&gt;would&lt;/em&gt; say that, right?) It’s because the tests can’t get at what those layers need. A state machine test has to press Reset and Select on exact frames and then ask the game which state it’s in. A scripted playthrough has to feed in the same key presses and get the same game every time, including in Game 3, where the random numbers decide where everything goes. A visual comparison has to render a room on demand and capture the picture. Right now, the key states are private, the random numbers come straight from the browser, the game’s current state is hidden, and each frame expects a real canvas to draw on. The geometry tests work because geometry is pure: give a function some numbers, check what comes back. Everything else is locked away from a test, so the only way to check it is to play.&lt;/p&gt;
&lt;p&gt;In fairness to my developer self, the seam is in the right place. Nearly everything a test needs passes through that one hardware file. But a seam is only useful if a test can actually reach through it, and right now this one is closed. Four small changes would open it.&lt;/p&gt;
&lt;p&gt;And here’s where I’d like you to put your tester hat on. Before reading on, look at &lt;em&gt;src/hardware.ts&lt;/em&gt; and &lt;em&gt;src/adventure.ts&lt;/em&gt; and ask yourself what you would change. What stops a test from pressing a key on frame 200? What stops it from replaying a Game 3 layout? What stops it from asking whether the game is in its Win state? Finding those blockers is a testing skill in its own right. It’s the difference between saying “we should have more tests” and saying exactly what stands in the way of them. I’ve put my four answers at the end of this post, so you can compare with yours, if you choose to give this a shot.&lt;/p&gt;
&lt;p&gt;I can tell you that with those in place, a scripted playthrough becomes a list of frame numbers and key changes, plus a seed and an expected end state. This matters for a variety of reasons. One is that an exploratory tester’s bug report could become one of these scripts. Two is that you could use this as the basis for some automation based on that exploration. Three is that you could use AI to assist with this scripting.&lt;/p&gt;
&lt;h2&gt;Heuristics for Exploratory Play&lt;/h2&gt;
&lt;p&gt;The heuristics I’m about to talk about need no code. Each one is a way of looking at the game that tends to find bugs. My advice to testers would be to pick a few per session rather than trying them all at once. And if you were actually testing this “for real,” I would say to keep an emulator running the original open for comparison. That’s exactly what I did, when I stepped out of my developer role on this and went into my tester role.&lt;/p&gt;
&lt;p&gt;That interleaving of roles is what I talked about regarding how &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://testerstories.com/2026/09/testing-knowledge-isnt-just-a-testing-skill/&quot;&gt;testing knowledge isn’t just a testing skill&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You might want to stop reading and explore the game a bit then come back and see which of the following you tried or thought of.&lt;/p&gt;
&lt;h3&gt;Moving through the World&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Walk every exit at every level. Room connections change between Games 1, 2 and 3, so an exit that works in one game can be wrong in another.&lt;/li&gt;
&lt;li&gt;Hug the walls. Slide along walls, into corners and through narrow gaps. Collision bugs live at edges, not in open space.&lt;/li&gt;
&lt;li&gt;Cross the screen edge slowly. Step back and forth across a room boundary, one press at a time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Objects&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Carry everything through everything. Take each object across a room edge, through a castle gate and over the bridge.&lt;/li&gt;
&lt;li&gt;Drop things in awkward places. Drop items inside walls, half off the screen and in a castle gateway as it closes.&lt;/li&gt;
&lt;li&gt;Lock yourself in. Use each key to close a castle behind you and check that you can still get out.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Creatures&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Collide on the same frame. Touch the sword just as a dragon bites. Let the bat grab what you’re holding. Let the magnet pull an object into a wall.&lt;/li&gt;
&lt;li&gt;Play keep-away with the bat. Follow it, block it, and see where stolen items end up.&lt;/li&gt;
&lt;li&gt;Test each dragon’s personality. Yorgle should flee the sword and the chalice. Grundle and Rhindle should hunt. Compare their speeds with the emulator.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Switches and States&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Interrupt every state. Press and release 1 or 2 while being eaten, while carrying, during a roar and after a win.&lt;/li&gt;
&lt;li&gt;Hold keys across transitions. As I said earlier, state changes fire when a key is released, so hold 1 or 2 down and release it at the worst possible moment.&lt;/li&gt;
&lt;li&gt;Flip difficulty mid-chase. The difficulty buttons take effect immediately, so toggle them while a dragon is chasing or roaring.&lt;/li&gt;
&lt;li&gt;Cover the switch matrix. Three game levels times four difficulty settings gives twelve configurations. Check each against the bite delays in the project’s difficulty notes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Input and the Browser&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Abuse the keyboard. Hold opposite arrows together. Mash several keys at once. Hold space while moving.&lt;/li&gt;
&lt;li&gt;Lose focus mid-move. Hold an arrow, then click another window or switch tabs, and come back. Check whether the player keeps moving.&lt;/li&gt;
&lt;li&gt;Leave and return. Put the tab in the background for a minute, then return. Watch whether the game briefly runs faster than normal.&lt;/li&gt;
&lt;li&gt;Change the display. Play on 60, 120 and 144 Hz screens, in full screen mode and in a small window. Check whether the arrow keys or space scroll the page.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Fidelity&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Find the &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://gaming-urban-legends.fandom.com/wiki/Adventure_Easter_Egg&quot;&gt;Easter egg&lt;/a&gt;. (For those who know the game, yes, I did implement it!) The secret dot and author room exercise several obscure mechanics at once. If that path works, a &lt;em&gt;lot&lt;/em&gt; of fidelity holds.&lt;/li&gt;
&lt;li&gt;Race the emulator. Play the same route in both versions and compare timing, sounds and colors at each step.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Heuristics for Reading the Code&lt;/h3&gt;
&lt;p&gt;These next heuristics are for testers who can read the TypeScript and the &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/main/docs/adventure.asm&quot;&gt;assembly&lt;/a&gt; side by side. Each one targets a way that a faithful-looking translation can still be wrong.&lt;/p&gt;
&lt;p&gt;Where a heuristic has a clear example in the original, I’ve linked straight to the lines in the annotated assembly listing, so you can see what the TypeScript is supposed to be matching. If you’re not used to reading 6502, the comments down the right-hand side do most of the work.&lt;/p&gt;
&lt;p&gt;Again, you might want to take a shot at this yourself before reading on.&lt;/p&gt;
&lt;h3&gt;Translation from 6502 Assembly&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Check the range math on every random call. Confirm that the result can reach every intended value and nothing outside them. This would have caught the Game 3 freeze that I’ll talk about below. In the original, &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/2c75bff7794bea65ddc9ae9dd08c3816fa8bcea2/docs/adventure.asm#L629-L639&quot;&gt;RandomizeLevel3&lt;/a&gt; stirs a counter into a pseudo-random number and then trims it with &lt;em&gt;and #$1f&lt;/em&gt;, which can only ever give 0 to 31.&lt;/li&gt;
&lt;li&gt;Wrap where the ROM wraps. The 2600 stores most values in one byte, so 255 plus 1 is 0. TypeScript numbers do not wrap, so any counter, timer or position that relies on overflow needs an explicit mask. The original’s &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/2c75bff7794bea65ddc9ae9dd08c3816fa8bcea2/docs/adventure.asm#L453-L458&quot;&gt;CheckInput&lt;/a&gt; counts on this: &lt;em&gt;inc&lt;/em&gt; rolls the low byte over to 0, and only that rollover bumps the high byte. That same low byte is the seed for the Game 3 shuffle.&lt;/li&gt;
&lt;li&gt;Check bit order against the hardware. The playfield registers are read in different directions, and the existing playfield tests pin this down. Apply the same suspicion to sprite and flag bits. The original’s &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/2c75bff7794bea65ddc9ae9dd08c3816fa8bcea2/docs/adventure.asm#L89-L98&quot;&gt;PrintDisplay&lt;/a&gt; writes each room line straight into PF0, PF1 and PF2, and the hardware draws PF1 in the opposite bit order from the other two.&lt;/li&gt;
&lt;li&gt;Translate comparisons exactly. The 6502 branches on carry and zero flags, which map to unsigned less-than and greater-or-equal. An inclusive bound written as exclusive is an off-by-one that only shows at the edge. The Game 3 &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/2c75bff7794bea65ddc9ae9dd08c3816fa8bcea2/docs/adventure.asm#L640-L645&quot;&gt;room bounds check&lt;/a&gt; is a good one to practice on: &lt;em&gt;bcc&lt;/em&gt; rejects a room below the lower bound, then &lt;em&gt;beq&lt;/em&gt; and &lt;em&gt;bcs&lt;/em&gt; accept the upper bound itself and reject anything above it. Both bounds are inclusive.&lt;/li&gt;
&lt;li&gt;Trace every table index. For a lookup such as the dragon bite delay table, work out the index for every level and difficulty and confirm it lands on the intended entry. The original &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/2c75bff7794bea65ddc9ae9dd08c3816fa8bcea2/docs/adventure.asm#L1219-L1225&quot;&gt;builds the index&lt;/a&gt; by taking one difficulty bit and merging in the level number, which is stored as 0, 2 or 4, and then reads the &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/2c75bff7794bea65ddc9ae9dd08c3816fa8bcea2/docs/adventure.asm#L1294-L1297&quot;&gt;DragonDiff table&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Game Structure&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Respect the frame split. The game spreads its work across three sub-frames, as the original did. Moving a call to a different sub-frame changes timing and the order of collisions. In the original’s &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/2c75bff7794bea65ddc9ae9dd08c3816fa8bcea2/docs/adventure.asm#L504-L539&quot;&gt;MainGameLoop&lt;/a&gt;, three waits for vertical sync divide the loop into those three frames, and the player’s movement is handled once in each, a different way each time.&lt;/li&gt;
&lt;li&gt;Look for state that survives a reset. List every module-level variable and check whether a full reset and a mid-game reset each restore it. A missed one produces a bug only on the second game. The original’s &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/2c75bff7794bea65ddc9ae9dd08c3816fa8bcea2/docs/adventure.asm#L558-L579&quot;&gt;CheckGameStart&lt;/a&gt; shows exactly what a mid-game reset restores: the player’s room and position, all three dragons, and the carried object. Objects aren’t touched. It also zeroes the note count, which the person who annotated the listing flagged with “(ops!??)”.&lt;/li&gt;
&lt;li&gt;Read the frame loop for catch-up. The loop advances its clock in fixed steps and runs one tick per display frame. After a long pause, it may tick on every display frame until it catches up, which is faster than normal on high refresh screens. Confirm or rule this out. The original never has this problem: its &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/2c75bff7794bea65ddc9ae9dd08c3816fa8bcea2/docs/adventure.asm#L196-L212&quot;&gt;DoVSYNC&lt;/a&gt; routine waits on a hardware timer every frame, so the game can’t run faster than the console.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Claims in Comments and Docs&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Treat comments as claims to test. For example, a comment says the Win state ignores the Reset key, while the Win state’s own handler returns to Game Select when Reset is released. Both can be true, but a test should pin down the exact behavior. And here the original can settle it. A &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/2c75bff7794bea65ddc9ae9dd08c3816fa8bcea2/docs/adventure.asm#L509-L517&quot;&gt;win&lt;/a&gt; marks the game as inactive, and &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://github.com/jeffnyman/adventure-uncartridged/blob/2c75bff7794bea65ddc9ae9dd08c3816fa8bcea2/docs/adventure.asm#L562-L564&quot;&gt;a Reset while the game is inactive&lt;/a&gt; goes straight back to the game select screen.&lt;/li&gt;
&lt;li&gt;Check that docs still match the code. The difficulty notes still refer to a source file that has since been split up. Stale docs mislead the next tester.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Build Itself&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Run the whole pipeline before merging a dependency update. Run the check, the tests and the build locally, not just the check. Then confirm CI passes on the merged commit, not only on the pull request.&lt;/li&gt;
&lt;li&gt;Keep toolchain versions pinned. A version range such as &lt;em&gt;latest&lt;/em&gt; lets any lockfile regeneration bring in a breaking change. This happened to me once.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Sample Charters&lt;/h3&gt;
&lt;p&gt;A charter gives an exploratory session a target and a purpose without scripting the steps. Each one below follows the form: explore a target, with some resources, to discover some information. Time boxes are suggestions.&lt;/p&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;Charter&lt;/td&gt;
&lt;td&gt;Track&lt;/td&gt;
&lt;td&gt;Time box&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explore Game 3 starts under all four difficulty settings, to discover whether every start gives a playable layout&lt;/td&gt;
&lt;td&gt;Human&lt;/td&gt;
&lt;td&gt;30 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explore the castle gates with each key, to discover whether the player can be wrongly locked in or out&lt;/td&gt;
&lt;td&gt;Human&lt;/td&gt;
&lt;td&gt;45 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explore the bat with each carriable object, to discover where stolen items end up and whether any can be lost&lt;/td&gt;
&lt;td&gt;Human&lt;/td&gt;
&lt;td&gt;45 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explore Reset and Select presses in every state, to discover transitions that leave the game inconsistent&lt;/td&gt;
&lt;td&gt;Human&lt;/td&gt;
&lt;td&gt;30 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explore dragon encounters side by side with the emulator, to discover differences in speed and bite timing&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;td&gt;60 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explore the game after leaving its tab in the background, to discover whether the frame loop runs fast while catching up&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;td&gt;30 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explore the reset code paths, to discover module-level state that survives a new game&lt;/td&gt;
&lt;td&gt;Technical&lt;/td&gt;
&lt;td&gt;60 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explore every random number call against the assembly, to discover range or distribution errors&lt;/td&gt;
&lt;td&gt;Technical&lt;/td&gt;
&lt;td&gt;45 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;At the end of a session, write down what was covered, what was found and what is still unexplored. The unexplored list becomes the next charter. Yet, look at those times! Now, even if those are built with a bit of buffer, it does make you think about regression testing, right?&lt;/p&gt;
&lt;p&gt;“But, Jeff, you fool. This is a port. Once it’s done, it’s done, right? Why would we need regression tests?” Well, new browser versions. Or an entirely new browser. Or maybe some styling updates for mobile. And so on.&lt;/p&gt;
&lt;h2&gt;But … but … what about AI ?!?&lt;/h2&gt;
&lt;p&gt;AI, as with any automation, can help with that time issue. For a tester, an AI agent that wins Games 1 and 2 is a test instrument, not a player. The question it answers is whether a route that wins on the original also wins here, on the same frames. And continues to do so over time.&lt;/p&gt;
&lt;p&gt;A key thing to understand about this game is that Games 1 and 2 are deterministic. Neither this port nor the original assembly uses randomness outside the Game 3 shuffle. Given the same key presses on the same frames, both games should play out identically every time. A winning run can therefore be recorded as an input sequence and replayed. If the port is faithful, the replay wins on the same frame as the original.&lt;/p&gt;
&lt;h3&gt;Three Kinds of Agent&lt;/h3&gt;
&lt;table&gt;
&lt;tr&gt;
&lt;td&gt;Approach&lt;/td&gt;
&lt;td&gt;Sees&lt;/td&gt;
&lt;td&gt;Acts by&lt;/td&gt;
&lt;td&gt;A win shows&lt;/td&gt;
&lt;td&gt;Main difficulty&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search-based solver&lt;/td&gt;
&lt;td&gt;Game state: rooms, objects, positions&lt;/td&gt;
&lt;td&gt;Planning a route through the room graph and steering to each target&lt;/td&gt;
&lt;td&gt;The win is reachable in this version&lt;/td&gt;
&lt;td&gt;Needs the testability changes. It plans rather than learns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reinforcement learning agent&lt;/td&gt;
&lt;td&gt;Screen pixels or game memory&lt;/td&gt;
&lt;td&gt;Trial and error over many thousands of runs&lt;/td&gt;
&lt;td&gt;A strategy learned on one version works on the other only if they match&lt;/td&gt;
&lt;td&gt;The game gives one reward, at the very end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vision language model&lt;/td&gt;
&lt;td&gt;Screenshots&lt;/td&gt;
&lt;td&gt;Choosing key presses and explaining why&lt;/td&gt;
&lt;td&gt;Little on its own, but it can follow a charter and describe oddities&lt;/td&gt;
&lt;td&gt;Far too slow for real time, so it needs a pause-and-step mode. It can misdescribe what it sees&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;h3&gt;The Agent I Built&lt;/h3&gt;
&lt;p&gt;Looking at that table, I went with the first option: a search-based solver. Partly that was a practical choice. A reinforcement learning agent would need thousands of runs to stumble onto a single reward at the very end (something my &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://testerstories.com/2017/07/pacumen-exploring-testing-and-ai/&quot;&gt;Pacumen project&lt;/a&gt; showed), and a vision model would be describing the screen long after the dragon had finished eating me. But mostly it was because Game 1 has no randomness. A plan that works once works every time (at least in this context), so planning beats learning.&lt;/p&gt;
&lt;p&gt;The goal was simple to state. Have an agent win Game 1 on its own, record every key press it made, and then play that recording back in the real game so I could watch it.&lt;/p&gt;
&lt;h3&gt;The Seam Pays Off&lt;/h3&gt;
&lt;p&gt;Remember the testability point about every contact with the browser going through one file? Well, that’s what made this possible. It wasn’t that “AI exists and it can do this for me.” It’s “I’ve instrumented this code in a way that AI can likely use to learn how to play the game just as a human would.” The agent runs the real game code in Node, with no browser at all. A small loader hook swaps &lt;em&gt;src/hardware.ts&lt;/em&gt; for a headless stand-in. The stand-in reads input from whatever the agent sets for each frame instead of the keyboard, draws into memory instead of onto a canvas, and doesn’t play sounds.&lt;/p&gt;
&lt;p&gt;Nothing else in the game changes. The agent calls &lt;em&gt;startGame&lt;/em&gt; once per frame, exactly as the browser’s loop would, and reads the game’s own state between frames: which room the player is in, what they’re carrying, whether a dragon is dead, whether a castle gate is open.&lt;/p&gt;
&lt;p&gt;This is the testability work from earlier, just done for a reason. Scriptable input and stepping frames directly stopped being suggestions the moment I needed them.&lt;/p&gt;
&lt;h3&gt;Test the Instrument First&lt;/h3&gt;
&lt;p&gt;To plan a route, the agent has to predict where the player will end up after each move. So it carries a model of the player’s movement. I didn’t write that model from a description of how I thought the game worked. It’s built from the game’s own collision checks and room tables, mirroring the movement code step by step: the six-unit moves, the room wraps at the screen edges, the castle entrances, the way a wall hit is undone.&lt;/p&gt;
&lt;p&gt;Here’s the thing, though. A model is a claim, and claims (ideally) get tested. Before trusting the planner with anything, a separate check drives the real game with random moves and compares every step against what the model predicted. The latest run checked 98,956 steps with zero mismatches.&lt;/p&gt;
&lt;p&gt;I think this is the most tester-flavored part of the whole exercise. An agent planning with a wrong model would fail in confusing ways, and I would have spent my time debugging the agent when the actual problem was its picture of the world. So the instrument gets tested before it’s used to test anything else.&lt;/p&gt;
&lt;h3&gt;How It Plays&lt;/h3&gt;
&lt;p&gt;Crucially, the agent doesn’t discover the route. I gave it the one a human would take, as a list of goals checked in order against the game state:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Take the yellow key.&lt;/li&gt;
&lt;li&gt;Open the yellow castle and go in.&lt;/li&gt;
&lt;li&gt;Take the sword.&lt;/li&gt;
&lt;li&gt;Kill the yellow dragon, then the green dragon.&lt;/li&gt;
&lt;li&gt;Swap the sword for the black key.&lt;/li&gt;
&lt;li&gt;Open the black castle and go in.&lt;/li&gt;
&lt;li&gt;Take the chalice and carry it home to the yellow castle.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notice: I gave it my intent; not the implementation. For each goal, the agent searches outward from the player’s position, breadth first, until it finds a position where the goal is met. That might be touching the key, or touching a dragon with the sword in hand. The search avoids anything it shouldn’t touch. In &lt;em&gt;Adventure&lt;/em&gt;, walking into a carriable object swaps it into your hand, so brushing past the bridge on the way to the key would quietly ruin the plan, since it would now pick up the bridge.&lt;/p&gt;
&lt;p&gt;Then it follows the path, one movement cycle at a time. If the game ever ends up somewhere the model didn’t predict, usually because a dragon or the bat moved something, the agent throws the plan away and searches again. When it’s chasing a dragon, it replans on every step, because the target keeps moving. If a dragon eats the player, it presses Reset and starts from the first goal it hasn’t completed yet.&lt;/p&gt;
&lt;p&gt;There’s one setting worth mentioning. The run uses the right difficulty switch at A, which makes dragons charge at the player instead of fleeing the sword. That sounds harder, but for an agent it’s easier. Why? Because the dragons come to the sword instead of the agent having to chase them across the map.&lt;/p&gt;
&lt;p&gt;But … you actually don’t have to kill the dragons at all to win the game. This was something I did not specifically tell the agent. Just as I didn’t tell &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://testerstories.com/2017/07/testing-learning-systems/&quot;&gt;Pac-Man everything it needed to know about ghosts and dots&lt;/a&gt;. So, realistically, the agent should have opted for difficulty B, right? The dragons just run away. Ah, but the agent knew, based on my constraints, that this would not test some of the most important conditions, such as the dragon chasing you to eat you.&lt;/p&gt;
&lt;h3&gt;The Result&lt;/h3&gt;
&lt;p&gt;The agent wins Game 1 in 3,368 frames, which is just under a minute of game time, and no dragon eats it along the way. Its log reads a lot like a test report:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
frame      2  game 1 started, difficulty left B, right A
frame     68  done: take the yellow key
frame    134  done: open the yellow castle
frame    140  done: enter the yellow castle
frame    185  done: take the sword
frame    371  done: kill the yellow dragon
frame    677  done: kill the green dragon
frame    731  done: swap the sword for the black key
frame   1889  done: open the black castle
frame   1901  done: enter the black castle
frame   2012  done: take the chalice
frame   3128  the chalice is home: game won
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output is a recording with one number per frame, where each bit stands for a key: up, down, left, right, fire, reset, select. Since Game 1 is deterministic, that recording is the whole game. Playing it back always produces the same win on the same frame.&lt;/p&gt;
&lt;p&gt;That also meant I could watch it. The browser version accepts a &lt;em&gt;?replay=&lt;/em&gt; parameter pointing at a recording. While it plays, the keyboard is ignored and inputs are applied per game frame rather than per unit of time, so the playback matches the recording whatever the display’s refresh rate. Here’s that run, filmed from the real page:&lt;/p&gt;
&lt;p&gt;I posted a &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://youtu.be/H0m7h90Nl-Q&quot;&gt;video of this on YouTube&lt;/a&gt; if you want to bask in the glory of AI Adventure!&lt;/p&gt;
&lt;p&gt;This is where the earlier point about bug reports becoming scripts gets concrete. A recording is a scripted playthrough. If a future change breaks something on the route, replaying the file will fail to win, or win on a different frame, and that’s a regression test nobody had to write by hand.&lt;/p&gt;
&lt;h3&gt;What It Proves and What It Doesn’t&lt;/h3&gt;
&lt;p&gt;I have to be careful here, since overclaiming is a failure mode we testers like to spot in others. The agent proves Game 1 can be won in &lt;em&gt;my&lt;/em&gt; version, by &lt;em&gt;this&lt;/em&gt; route, under &lt;em&gt;these&lt;/em&gt; switch settings. It doesn’t prove my version matches the 1979 original. The agent uses my code’s collision checks to plan, so if my translation of the walls were wrong, the agent would happily plan around my wrong walls.&lt;/p&gt;
&lt;p&gt;Fidelity needs the original in the loop. The next step would be to get a winning run on the original, for example through the &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://ale.farama.org/environments/adventure/&quot;&gt;Arcade Learning Environment&lt;/a&gt;, which supports &lt;em&gt;Adventure&lt;/em&gt; and rewards getting the chalice to the yellow castle. That run would be recorded frame by frame, with the environment’s random input repeats turned off. Then you would replay it in my version and compare the player’s room and position on every frame. The first frame where they differ points at the bug.&lt;/p&gt;
&lt;p&gt;One small fidelity check has already passed, though. The original’s win check and mine agree. Both test whether the chalice is in the yellow castle’s inner room.&lt;/p&gt;
&lt;h3&gt;Heuristics for Judging Agent Results&lt;/h3&gt;
&lt;p&gt;Building this let me demonstrate a few heuristics that apply to any agent you point at software, not just this game:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Validate the model before trusting the planner. Whatever the agent believes about the system, check that belief against the real system first.&lt;/li&gt;
&lt;li&gt;Treat a surprising win as a bug lead. If an agent wins by passing through a wall or scoring without the chalice, check whether the original allows the same move.&lt;/li&gt;
&lt;li&gt;Don’t read a failure as evidence. An agent that never wins may simply be a weak agent. Only a replay that diverges is a clean signal.&lt;/li&gt;
&lt;li&gt;Keep determinism intact. The browser’s animation loop varies with the display, so only frame stepping gives repeatable runs.&lt;/li&gt;
&lt;li&gt;Check what an agent claims. Confirm every reported problem against a recording or the game state before filing it. The agent’s log is a claim, just like a comment in the code.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Case Study: The Game 3 Freeze&lt;/h2&gt;
&lt;p&gt;Starting Game 3 would freeze the browser tab. The bug was found by reading code with the first technical heuristic, confirmed by tests rather than in a browser.&lt;/p&gt;
&lt;p&gt;The defect was that Game 3 scatters objects by picking a random room and retrying until the room is within that object’s allowed range. The room was picked like this:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;pre&gt;let room = Math.floor(random()) * 0x1f;&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The random number is always below 1, so rounding it down always gives 0, and every pick was room 0. No object is allowed in room 0, so the retry loop never ended. The original assembly masks a random byte down to the range 0 to 31. The fix scales first and rounds second:&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;pre&gt;const room = Math.floor(rng() * 0x20);&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I literally fixed that bug as I was writing this post so the operative question is why did it survive for so long? Well, the room pick was buried inside a private setup function, so no test could reach it. It also only runs when Game 3 starts, and no existing test drove the game through a level start of any kind.&lt;/p&gt;
&lt;p&gt;How would each track have caught it? Well, that’s fairly simple. A tester running the first sample charter would have hit the freeze within seconds of choosing Game 3. A tester applying the range-math heuristic would have spotted it from the single line above, without running anything.&lt;/p&gt;
&lt;p&gt;If you check the repo, it’s about four months old at the time of writing. That means if this was a real bug that I had not seeded, it would have been live for four months. Maybe that would have mattered, maybe not. It depends if anyone had tried to play Game level 3.&lt;/p&gt;
&lt;p&gt;Taking off my tester role and putting on my developer role, the fix was simple. The room pick became a small function that accepts its random source. Its tests feed it scripted values and give up after a fixed number of calls, so a regression fails quickly instead of hanging the test run. The new tests were confirmed to fail on the old code before the fix went in.&lt;/p&gt;
&lt;p&gt;Okay, our trip down adventure lane is probably at an end here, and I did promise an answer key from earlier, so here that is.&lt;/p&gt;
&lt;h2&gt;Answer Key: Four Changes for Testability&lt;/h2&gt;
&lt;p&gt;These are the four changes I came up with for the exercise in the Testability section. If you found others, I would genuinely like to hear about them.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Make randomness seedable.&lt;/strong&gt; &lt;em&gt;random()&lt;/em&gt; in the hardware layer calls &lt;em&gt;Math.random&lt;/em&gt; directly. A seeded generator would let any Game 3 layout or bat behavior be replayed exactly. Showing the seed somewhere on screen would let a tester report a strange layout that could then be reproduced.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make input scriptable.&lt;/strong&gt; Key state lives in private variables set by keyboard events. A test hook that sets the joystick and switches directly, or a mock of the hardware module, would let tests press and release keys on exact frames.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step frames directly.&lt;/strong&gt; Tests can call &lt;em&gt;startGame&lt;/em&gt; once per frame with no animation loop, as long as drawing and sound are stubbed. The test setup already stubs browser audio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expose state for inspection.&lt;/strong&gt; Object positions are already exported from the object data. The current game state, level and frame count are private, so tests can’t yet check which state the game is in.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As it turned out, building the Game 1 agent did two of these for me. Its headless hardware layer makes input scriptable and lets a test step through frames directly, and the agent’s recording is a scripted playthrough in all but name. Seeding randomness wasn’t needed for Game 1, since it has no randomness, so that one, and exposing the game’s state, are still open.&lt;/p&gt;
&lt;h2&gt;What Next?&lt;/h2&gt;
&lt;p&gt;This &lt;em&gt;Adventure&lt;/em&gt; may be over, but I don’t think all test adventures are over. In the next post, I’ll show another game I worked on. Prepare yourself to run the gauntlet.&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.addtoany.com/share#url=https%3A%2F%2Ftesterstories.com%2F2026%2F10%2Ftesting-adventure%2F&amp;amp;title=Testing%20Adventure&quot; data-a2a-url=&quot;https://testerstories.com/2026/10/testing-adventure/&quot; data-a2a-title=&quot;Testing Adventure&quot;&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; src=&quot;https://static.addtoany.com/buttons/favicon.png&quot; alt=&quot;Share&quot;&gt;&lt;/a&gt;&lt;/p&gt;</summary><author><name>Jeff Nyman</name></author><source gr:stream-id="feed/http://testerstories.com/feed/"><id>tag:google.com,2005:reader/feed/http://testerstories.com/feed/</id><title type="html">Stories from a Software Tester</title><link rel="alternate" href="https://testerstories.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792596188000"><id gr:original-id="tag:blogger.com,1999:blog-4468368081571379042.post-5437870610465565686">tag:google.com,2005:reader/item/0000063200000064</id><category term="QA Jobs"></category><category term="SDET Jobs"></category><category term="Software Testing Jobs"></category><category term="Testing Jobs"></category><title type="html">Software Testing &amp;amp; QA Jobs – October 21, 2026 | Daily Job Openings</title><published>2026-10-21T15:23:08Z</published><updated>2026-10-21T15:23:08Z</updated><link rel="alternate" href="https://qa-qcarena.blogspot.com/2026/10/software-testing-qa-jobs-october-21.html" type="text/html"></link><summary type="html">&lt;p style=&quot;text-align: center&quot;&gt; &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2l9DdexgHXvmP_OX6hUC48QKl428EReSlMpezKeU_XsyDOf90rfxtzwBbX3Sxcr_lhySaw6TxrHRoF_RmmFGK9fGuiKyeq0Cg-CXLWDwYZgeyZfyJ4zOwuRwe6Jom0HGAxjHeW5gaa-7ATuEZpqFECT212VsIfU3SKhvLMFxmla-2Hh3knO6KwEUTcxA/s1672/Software%20Testing%20&amp;amp;%20QA%20Jobs%20Banner.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em&quot;&gt;&lt;img width=&quot;400&quot; height=&quot;225&quot; style=&quot;border: 0px solid black&quot; data-original-height=&quot;941&quot; data-original-width=&quot;1672&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2l9DdexgHXvmP_OX6hUC48QKl428EReSlMpezKeU_XsyDOf90rfxtzwBbX3Sxcr_lhySaw6TxrHRoF_RmmFGK9fGuiKyeq0Cg-CXLWDwYZgeyZfyJ4zOwuRwe6Jom0HGAxjHeW5gaa-7ATuEZpqFECT212VsIfU3SKhvLMFxmla-2Hh3knO6KwEUTcxA/w400-h225/Software%20Testing%20&amp;amp;%20QA%20Jobs%20Banner.png&quot;&gt; &lt;/a&gt;&lt;/p&gt;&lt;p style=&quot;text-align: left&quot;&gt;

&lt;/p&gt;&lt;h2 style=&quot;margin-bottom: 0cm; text-align: left&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Software Testing &amp;amp; QA Jobs –
October 21, 2026&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - QC Manager - TSD&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Ubisoft&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - 8+ years&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Pune, Maharashtra&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job
Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;
- &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://jobs.smartrecruiters.com/Ubisoft2/744000156764558-qc-manager-tsd&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Senior Quality Analyst (QA)&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Heaptrace&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - 3+ years&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Pune, Maharashtra&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.heaptrace.com/job-Positions/senior-quality-analyst-qa&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - SDET Test Engineer B3&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Wipro&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Minimum 5+ years in test
automation&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Bengaluru, Karnataka&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://careers.wipro.com/job/Bengaluru-SDET-Test-Engineer-B3-IND-560035/210942-en_US/&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Software QA Engineer&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Applied Materials&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - 5-12 years&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Bengaluru, Karnataka&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://jobs.appliedmaterials.com/job/bengaluru/software-qa-engineer/95/102150828576&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Playwright + Agentic AI&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Wipro&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - 7–12 years&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Bengaluru, Karnataka;
Hyderabad and Chennai are also listed&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://careers.wipro.com/job/Bengaluru-Playwright-%20-Agentic-AI-IND-560035/206383-en_US/&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Senior Test Automation
Engineer&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Capgemini&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Not specified&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Pune, Maharashtra&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://careers.capgemini.com/job/Pune-Senior-Test-Automation-Engineer/1425056033/&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Senior Test Automation
Engineer&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Capgemini&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Not specified&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Hyderabad, Telangana&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://careers.capgemini.com/job/Hyderabad-Senior-Test-Automation-Engineer/1448845933&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Playwright Test Automation&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Capgemini&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - 6+ years (at least 2 years of
hands-on Playwright)&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Pune, Maharashtra / Bengaluru,
Karnataka / Hyderabad, Telangana / Chennai, Tamil Nadu&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://careers.capgemini.com/job/Bangalore-Playwright-Test-Automation/1365697133/&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Guidewire Test Engineer&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Capgemini&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - 4+ years of &lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;Experience&lt;/i&gt;&lt;/b&gt;
with Guidewire applications and test automation tools&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Bengaluru, Karnataka /
Hyderabad, Telangana&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://careers.capgemini.com/job/Hyderabad-Guidewire-Test-Engineer/1437433733&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Senior Test Automation
Engineer&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - VOIS&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Not specified&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Pune, Maharashtra&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://opportunities.vodafone.com/job/Pune-Senior-Test-Automation-Engineer/1434761433/&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Agile Test Automation
Engineer, Quality Assurance&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - London Stock Exchange Group
(LSEG)&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Not mentioned&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Bengaluru, Karnataka&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://lseg.wd3.myworkdayjobs.com/en-US/Careers/job/Agile-Test-Automation-Engineer--Quality-Assurance_R0108606-1&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Manager, Tech Quality
Assurance&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - GALE&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - 8 to 10 years&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Bengaluru, Karnataka&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://jobs.lever.co/galepartners/b182edca-ae4d-4bee-abdf-1418a851cb8a&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Software Development Engineer
in Test (SDET)&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Kobie&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Mid-level (relevant automation
and testing framework &lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;Experience&lt;/i&gt;&lt;/b&gt;)&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Bengaluru, Karnataka&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://jobs.lever.co/kobie/f05856c8-23be-4024-b417-d19a342a1027&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Position&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Senior SDET - Hadoop
Observability (Pulse)&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Company&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Acceldata&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Experience&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - 4-8 years&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Location&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - Bengaluru, Karnataka&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;b style=&quot;mso-bidi-font-weight: normal&quot;&gt;&lt;i style=&quot;mso-bidi-font-style: normal&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Job Description&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt; - &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://jobs.lever.co/acceldata/495913ff-d0ef-4234-b44e-ac2470c53540&quot;&gt;Click here&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;

&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;br&gt;&lt;/p&gt;&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Happy Job Hunting. &lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Segoe UI Emoji&amp;quot;,sans-serif; mso-bidi-font-family: &amp;quot;Segoe UI Emoji&amp;quot;&quot;&gt;😊&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Know someone who might find this
useful? Feel free to share it with them. &lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Segoe UI Emoji&amp;quot;,sans-serif; mso-bidi-font-family: &amp;quot;Segoe UI Emoji&amp;quot;&quot;&gt;😊&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;margin-bottom: 0cm&quot;&gt;&lt;span style=&quot;font-family: &amp;quot;Trebuchet MS&amp;quot;,sans-serif&quot;&gt;Happy Learning | Happy Testing |
Happy Job Hunting&lt;/span&gt;&lt;span style=&quot;font-family: &amp;quot;Segoe UI Emoji&amp;quot;,sans-serif; mso-bidi-font-family: &amp;quot;Segoe UI Emoji&amp;quot;&quot;&gt;👍&lt;/span&gt;&lt;/p&gt;</summary><author><name>YOGESH KHAIRNAR</name></author><source gr:stream-id="feed/https://qa-qcarena.blogspot.com/feeds/posts/default?alt=rss"><id>tag:google.com,2005:reader/feed/https://qa-qcarena.blogspot.com/feeds/posts/default?alt=rss</id><title type="html">QA-QC Arena</title><link rel="alternate" href="https://qa-qcarena.blogspot.com/" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792593119000"><id gr:original-id="10.1002/stvr.70030">tag:google.com,2005:reader/item/00000fe100000013</id><category term="RESEARCH ARTICLE"></category><title type="html">Robust Fine‐Tuning Based on Fuzz Testing for Chinese BERT‐Based PLMs</title><published>2026-10-21T14:31:59Z</published><updated>2026-10-21T14:31:59Z</updated><link rel="alternate" href="https://onlinelibrary.wiley.com/doi/10.1002/stvr.70030?af=R" type="text/html"></link><summary type="html">&lt;img class=&quot;bqrUnknownImgSize&quot; src=&quot;https://onlinelibrary.wiley.com/cms/asset/6dcdb183-d43a-4615-a8a6-5f9fb594cac7/stvr70030-toc-0001-m.png&quot; alt=&quot;Robust Fine-Tuning Based on Fuzz Testing for Chinese BERT-Based PLMs&quot;&gt;
&lt;p&gt;RoFuBERT is a robust fine-tuning framework designed to extract Chinese basis, glyphs, pinyin and semantics, which are used for fuzz testing of Chinese BERT-based Pre-trained Language Models (PLMs). The experiment demonstrates that, compared with the two baselines, RoFuBERT efficiently improves the adversarial robustness of models. &lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;ABSTRACT&lt;/h2&gt;
&lt;p&gt;With the development of Pre-trained Language Models (PLMs), a wide variety of Chinese Natural Language Processing (NLP) tasks benefit from the Bidirectional Encoder Representations of Transformers (BERT) structure. Unfortunately, Chinese BERT-based PLMs are vulnerable to adversarial attacks, exposing robustness issues. It inspired numerous defence works devoted to improving the model robustness. However, existing pre-training methods incur substantial time costs, while fine-tuning methods need more evidence to demonstrate how well the target model has been defended. This work aims to bridge this gap. We propose RoFuBERT, a robust fine-tuning framework using fuzz testing for Chinese BERT-based PLMs. We extract Chinese basis, glyph, pinyin and semantic features of downstream NLP tasks during fine-tuning. We integrate them into fuzz testing and evaluate the testing completeness of the model under adversarial attacks. Finally, we retrain the model for robust fine-tuning. Our evaluation shows that, compared with the two baselines, RoFuBERT efficiently improves adversarial robustness of models. RoFuBERT takes only 0.69 times more time, and the reduction in adversarial attack success rate is improved by 37.76%. The modification rate of the adversarial samples that can deceive models is improved by 5.19%, on average.&lt;/p&gt;</summary><author><name>Lichao Feng, Xingya Wang, Chunrong Fang, Zhenyu Chen, Zhihong Zhao</name></author><source gr:stream-id="feed/https://onlinelibrary.wiley.com/action/showFeed?type=etoc&amp;feed=rss&amp;jc=10991689"><id>tag:google.com,2005:reader/feed/https://onlinelibrary.wiley.com/action/showFeed?type=etoc&amp;feed=rss&amp;jc=10991689</id><title type="html">Wiley: Software Testing, Verification and Reliability: Table of Contents</title><link rel="alternate" href="https://onlinelibrary.wiley.com/journal/10991689?af=R" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792593119000"><id gr:original-id="10.1002/stvr.70017">tag:google.com,2005:reader/item/00000fe100000012</id><category term="ISSUE INFORMATION"></category><title type="html">Issue Information</title><published>2026-10-21T14:31:59Z</published><updated>2026-10-21T14:31:59Z</updated><link rel="alternate" href="https://onlinelibrary.wiley.com/doi/10.1002/stvr.70017?af=R" type="text/html"></link><summary type="html">&lt;p&gt;No abstract is available for this article.&lt;/p&gt;</summary><author><name></name></author><source gr:stream-id="feed/https://onlinelibrary.wiley.com/action/showFeed?type=etoc&amp;feed=rss&amp;jc=10991689"><id>tag:google.com,2005:reader/feed/https://onlinelibrary.wiley.com/action/showFeed?type=etoc&amp;feed=rss&amp;jc=10991689</id><title type="html">Wiley: Software Testing, Verification and Reliability: Table of Contents</title><link rel="alternate" href="https://onlinelibrary.wiley.com/journal/10991689?af=R" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792562280000"><id gr:original-id="https://karlosmid.com/2026/10/my-learning-bits-for-wednesday-21st-october-2026">tag:google.com,2005:reader/item/00000563000001e2</id><category term="learning"></category><category term="quantum-mechanics"></category><category term="physics"></category><category term="ai"></category><category term="llms"></category><category term="ethics"></category><title type="html">My learning bits for Wednesday of 21st October 2026</title><published>2026-10-21T05:58:00Z</published><updated>2026-10-21T05:58:00Z</updated><link rel="alternate" href="https://karlosmid.com/2026/10/my-learning-bits-for-wednesday-21st-october-2026/" type="text/html"></link><summary type="html">&lt;p&gt;Here are my learning bits for today:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Read about &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://www.quantamagazine.org/in-hilbert-space-all-things-are-quantumly-possible-20260826/&quot; rel=&quot;noopener noreferrer&quot;&gt;one space to rule them all in quantum mechanics&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://grox.io/blog/57-read-the-label/&quot; rel=&quot;noopener noreferrer&quot;&gt;What is the price tag of LLMs?&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</summary><author><name></name></author><source gr:stream-id="feed/https://blog.tentamen.eu/feed/"><id>tag:google.com,2005:reader/feed/https://blog.tentamen.eu/feed/</id><title type="html">Tentamen Software Testing Blog</title><link rel="alternate" href="https://karlosmid.com/" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792526019000"><id gr:original-id="https://chroniclesofapragmaticprogrammer.substack.com/p/kafka-para-quem-move-dinheiro">tag:google.com,2005:reader/item/00000a2700000070</id><title type="html">Kafka para quem move dinheiro</title><published>2026-10-20T19:53:39Z</published><updated>2026-10-20T19:53:39Z</updated><link rel="alternate" href="https://chroniclesofapragmaticprogrammer.substack.com/p/kafka-para-quem-move-dinheiro" type="text/html"></link><summary type="html">&lt;p&gt;Quinta-feira, 11 de setembro, 18h47. Uma instituição de pagamentos de médio porte processa o arquivo de retorno da liquidação do dia: 41 mil títulos, R$ 9,3 milhões. O fechamento roda em oito minutos, como sempre.&lt;/p&gt;&lt;p&gt;Às 19h02, o time de conciliação abre o chamado. Dezoito mil títulos foram baixados duas vezes. O saldo de uma carteira inteira ficou negativo.&lt;/p&gt;&lt;p&gt;Rafael, tech lead de plantão, abre os painéis antes mesmo de sentar. Broker verde. Consumidor verde. Nenhum alerta de infraestrutura a noite toda. Zero mensagem perdida — o lag do tópico tem um dente de serra de quarenta segundos às 18h58 e volta ao normal sozinho.&lt;/p&gt;&lt;p&gt;Ele desce para o log da aplicação. Às 18h58 o rolling restart do serviço de baixa trocou os seis pods do grupo. Rotina: aconteceu quarenta vezes naquela semana, e nas quarenta não deu problema. Naquela, cada pod processou o lote que tinha em mãos, gravou tudo no banco, e levou SIGKILL no fim do prazo de encerramento — antes de informar ao Kafka até onde tinha lido. Os substitutos subiram, perguntaram ao Kafka onde continuar, e o Kafka respondeu com a única verdade que tinha: o último ponto confirmado em cada partição. Dezoito mil títulos voltaram.&lt;/p&gt;&lt;p&gt;Na retrospectiva de segunda, alguém faz a pergunta que metade da sala estava pensando:&lt;/p&gt;&lt;p&gt;&lt;mark data-color=&quot;#fff2cc&quot; style=&quot;background-color: rgb(255, 242, 204); color: rgb(0, 0, 0)&quot;&gt;“Mas o Kafka não garante que cada mensagem é entregue uma vez só?”&lt;/mark&gt;&lt;/p&gt;&lt;p&gt;Não. Nunca garantiu. E o fato de metade da sala achar que sim é o incidente de verdade.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;p&gt;O caso é apenas ilustrativo e a instituição também. &lt;mark data-color=&quot;#fff2cc&quot; style=&quot;background-color: rgb(255, 242, 204); color: rgb(0, 0, 0)&quot;&gt;Mas o incidente existe: esse padrão — infraestrutura toda verde, dado duplicado, ninguém sabendo explicar — é um dos chamados mais comuns em time de pagamentos, e quase nunca é culpa do broker.&lt;/mark&gt;&lt;/p&gt;&lt;p&gt;Este artigo é a anatomia do Kafka escrita para quem move dinheiro. No &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/anatomia-do-kafka&quot;&gt;Anatomia do Kafka&lt;/a&gt; eu contei como a ferramenta funciona por dentro. Aqui eu conto o que muda quando o que trafega no tópico é liquidação, lançamento contábil e ordem de pagamento —&lt;strong&gt; e também corrijo coisas que eu mesmo escrevi lá, porque o produto mudou desde então e porque algumas daquelas frases já nasceram imprecisas.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;O texto serve a quem é técnico e a quem não é. Toda parte que exige saber o que é um commit de offset vem com a tradução ao lado, e nenhum bloco de código aparece sem um parágrafo em português antes dele explicando o que ele faz.&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;url&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/p/kafka-para-quem-move-dinheiro?utm_source=substack&amp;amp;utm_medium=email&amp;amp;utm_content=share&amp;amp;action=share&amp;quot;,&amp;quot;text&amp;quot;:&amp;quot;Compartilhar&amp;quot;}&quot; data-component-name=&quot;CaptionedButtonToDOM&quot;&gt;&lt;div&gt;&lt;p&gt;Este post é público, então fique à vontade para compartilhá-lo.&lt;/p&gt;&lt;/div&gt;&lt;p data-attrs=&quot;{&amp;quot;url&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/p/kafka-para-quem-move-dinheiro?utm_source=substack&amp;amp;utm_medium=email&amp;amp;utm_content=share&amp;amp;action=share&amp;quot;,&amp;quot;text&amp;quot;:&amp;quot;Compartilhar&amp;quot;}&quot; data-component-name=&quot;ButtonCreateButton&quot;&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/kafka-para-quem-move-dinheiro?utm_source=substack&amp;amp;utm_medium=email&amp;amp;utm_content=share&amp;amp;action=share&quot;&gt;&lt;span&gt;Compartilhar&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;O que é o Kafka&lt;/h2&gt;&lt;p&gt;O Kafka foi criado pelo LinkedIn em 2010 para um problema que hoje é de todo mundo: fazer muitos sistemas trocarem um volume alto de dados em tempo real sem se derrubarem. Mais tarde foi doado para a Apache Software Foundation.&lt;/p&gt;&lt;p&gt;Fazer partes de um sistema distribuído conversarem sem perder dado nem chegar tarde é difícil em qualquer domínio. Num banco isso é mais crítico ainda, porque “chegou tarde” tem outros nomes: quebra de SLA, multa regulatória, cliente sem saldo no caixa eletrônico num sábado.&lt;/p&gt;&lt;p&gt;Pense numa instituição que processa Pix. Cada transação precisa ser comunicada ao motor de limites, ao antifraude, ao razão contábil, ao extrato do cliente e ao arquivo regulatório. Se essas comunicações forem chamadas diretas de um sistema para o outro, qualquer um deles fora do ar derruba a cadeia inteira — e você descobre isso às duas da manhã, quando o antifraude reinicia e o serviço de transação começa a estourar timeout.&lt;/p&gt;&lt;p&gt;Para entender como o Kafka resolve isso, pense nele como um sistema de correios. Ele recebe mensagens de produtores — os sistemas que enviam dados — e as entrega para os consumidores, os sistemas que precisam desses dados.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Produtores&lt;/strong&gt; são os remetentes, que postam as mensagens.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consumidores&lt;/strong&gt; são os destinatários, que recebem essas mensagens.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;O Kafka&lt;/strong&gt; é o correio que organiza, armazena e distribui tudo de forma ordenada.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;E aqui está a propriedade que faz toda a diferença num sistema financeiro: o correio não exige que o destinatário esteja em casa na hora da postagem. O remetente posta e vai embora. O destinatário retira quando puder. Essa é a palavra que vale guardar do artigo inteiro — &lt;strong&gt;assíncrono&lt;/strong&gt;.&lt;/p&gt;&lt;p&gt;Isso muda o desenho da sua arquitetura. Num modelo de chamadas diretas, cinco serviços que precisam conversar entre si geram dez ligações, vinte caminhos contando ida e volta, e cada caminho é um ponto de falha que alguém precisa monitorar, repetir e explicar na auditoria. Com um tópico no meio, cada serviço conhece o tópico e mais nada.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!kklU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F156d2420-c91d-4694-a021-d256475efb99_3200x1554.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!kklU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F156d2420-c91d-4694-a021-d256475efb99_3200x1554.png 424w, https://substackcdn.com/image/fetch/$s_!kklU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F156d2420-c91d-4694-a021-d256475efb99_3200x1554.png 848w, https://substackcdn.com/image/fetch/$s_!kklU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F156d2420-c91d-4694-a021-d256475efb99_3200x1554.png 1272w, https://substackcdn.com/image/fetch/$s_!kklU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F156d2420-c91d-4694-a021-d256475efb99_3200x1554.png 1456w&quot; sizes=&quot;auto, 100vw&quot;&gt;&lt;img width=&quot;1000&quot; height=&quot;485&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/156d2420-c91d-4694-a021-d256475efb99_3200x1554.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:707,&amp;quot;width&amp;quot;:1456,&amp;quot;resizeWidth&amp;quot;:1000,&amp;quot;bytes&amp;quot;:208734,&amp;quot;alt&amp;quot;:&amp;quot;Figura 1. Cinco serviços conversando em pares contra os mesmos cinco ligados a um tópico. As dez ligações entre pares viram cinco, e nenhuma delas depende de o destino estar no ar.&amp;quot;,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/220079580?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F156d2420-c91d-4694-a021-d256475efb99_3200x1554.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt=&quot;Figura 1. Cinco serviços conversando em pares contra os mesmos cinco ligados a um tópico. As dez ligações entre pares viram cinco, e nenhuma delas depende de o destino estar no ar.&quot; title=&quot;Figura 1. Cinco serviços conversando em pares contra os mesmos cinco ligados a um tópico. As dez ligações entre pares viram cinco, e nenhuma delas depende de o destino estar no ar.&quot; data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!kklU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F156d2420-c91d-4694-a021-d256475efb99_3200x1554.png 424w, https://substackcdn.com/image/fetch/$s_!kklU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F156d2420-c91d-4694-a021-d256475efb99_3200x1554.png 848w, https://substackcdn.com/image/fetch/$s_!kklU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F156d2420-c91d-4694-a021-d256475efb99_3200x1554.png 1272w, https://substackcdn.com/image/fetch/$s_!kklU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F156d2420-c91d-4694-a021-d256475efb99_3200x1554.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!kklU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F156d2420-c91d-4694-a021-d256475efb99_3200x1554.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Figura 1. Cinco serviços conversando em pares contra os mesmos cinco ligados a um tópico. As dez ligações entre pares viram cinco, e nenhuma delas depende de o destino estar no ar.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Agora, vamos falar da estrutura básica da mensagem.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;Os elementos de uma mensagem&lt;/h2&gt;&lt;p&gt;As mensagens — também chamadas de eventos ou registros — são a unidade de tudo que trafega. Vale entender do que elas são feitas.&lt;/p&gt;&lt;p&gt;Aqui eu preciso corrigir o artigo anterior logo de cara. Lá eu escrevi que as mensagens trafegam normalmente em JSON. Isso é verdade em blog post e é raro em banco. &lt;strong&gt;Para o Kafka, chave e valor são bytes.&lt;/strong&gt; Ele não sabe e não se importa com o que tem dentro. O que você escolhe colocar ali é decisão de arquitetura, e em sistema financeiro a escolha quase sempre é Avro ou Protobuf com um registro de esquemas, por três motivos: o payload fica muito menor em alto volume, o contrato entre os times fica explícito e versionado, e uma mudança incompatível de esquema é barrada na publicação em vez de explodir dentro do consumidor seis horas depois.&lt;/p&gt;&lt;p&gt;Feita a correção, estes são os elementos de um registro:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Key (chave).&lt;/strong&gt; Opcional, mas é a decisão mais importante que você toma. A chave determina em qual partição a mensagem cai, e partição é a única unidade em que o Kafka garante ordem. Se você usa o identificador da conta como chave, todos os eventos daquela conta caem na mesma partição e são lidos na ordem em que aconteceram. Se você deixa a chave nula, não há ordem nenhuma para aquele fluxo.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Value (valor).&lt;/strong&gt; O dado em si — o evento, o lançamento, a ordem de pagamento. É o conteúdo da carta.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Timestamp.&lt;/strong&gt; O carimbo de tempo do registro. Duas observações que importam em banco: por padrão ele é o momento em que o produtor criou a mensagem, mas o tópico pode ser configurado para usar o momento em que o broker gravou. São coisas diferentes, e em conciliação essa diferença aparece. Guarde: o carimbo do correio não é a data escrita na carta.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Headers (cabeçalhos).&lt;/strong&gt; Metadados opcionais, em pares de chave e valor, que viajam ao lado do payload sem sujá-lo. É onde mora o seu identificador de correlação, o identificador de rastreio distribuído, a versão do esquema, a origem do evento. Em sistema financeiro, o header é o que permite responder “de onde veio esse lançamento?” seis meses depois, numa auditoria, sem abrir o payload.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Partition e offset.&lt;/strong&gt; Não são campos que você preenche. São o endereço que o Kafka atribui à mensagem depois de gravá-la: em qual partição ela caiu e qual número de ordem recebeu lá dentro. Vamos falar muito dos dois daqui a pouco.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;E aqui vai a segunda correção ao artigo antigo: &lt;strong&gt;compressão não é um campo da mensagem.&lt;/strong&gt; Ela é uma configuração do produtor, aplicada ao lote inteiro de registros, não a cada registro individualmente. Essa distinção não é pedantismo — é por isso que compressão no Kafka funciona tão bem. Mil lançamentos contábeis parecidos comprimem muito melhor juntos do que um a um, e é por isso que lotes maiores reduzem drasticamente o custo de rede em fluxos de alto volume.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!Wt7W!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb3c7e5a-376b-459f-9fae-f3439e4e8d7a_3200x1596.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!Wt7W!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb3c7e5a-376b-459f-9fae-f3439e4e8d7a_3200x1596.png 424w, https://substackcdn.com/image/fetch/$s_!Wt7W!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb3c7e5a-376b-459f-9fae-f3439e4e8d7a_3200x1596.png 848w, https://substackcdn.com/image/fetch/$s_!Wt7W!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb3c7e5a-376b-459f-9fae-f3439e4e8d7a_3200x1596.png 1272w, https://substackcdn.com/image/fetch/$s_!Wt7W!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb3c7e5a-376b-459f-9fae-f3439e4e8d7a_3200x1596.png 1456w&quot; sizes=&quot;auto, 100vw&quot;&gt;&lt;img width=&quot;1000&quot; height=&quot;498&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/fb3c7e5a-376b-459f-9fae-f3439e4e8d7a_3200x1596.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:726,&amp;quot;width&amp;quot;:1456,&amp;quot;resizeWidth&amp;quot;:1000,&amp;quot;bytes&amp;quot;:306594,&amp;quot;alt&amp;quot;:&amp;quot;Figura 2. O que é campo de protocolo e o que é conteúdo do payload. Compressão e particionamento acontecem fora do registro, no lote e no produtor.&amp;quot;,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/220079580?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb3c7e5a-376b-459f-9fae-f3439e4e8d7a_3200x1596.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt=&quot;Figura 2. O que é campo de protocolo e o que é conteúdo do payload. Compressão e particionamento acontecem fora do registro, no lote e no produtor.&quot; title=&quot;Figura 2. O que é campo de protocolo e o que é conteúdo do payload. Compressão e particionamento acontecem fora do registro, no lote e no produtor.&quot; data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!Wt7W!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb3c7e5a-376b-459f-9fae-f3439e4e8d7a_3200x1596.png 424w, https://substackcdn.com/image/fetch/$s_!Wt7W!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb3c7e5a-376b-459f-9fae-f3439e4e8d7a_3200x1596.png 848w, https://substackcdn.com/image/fetch/$s_!Wt7W!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb3c7e5a-376b-459f-9fae-f3439e4e8d7a_3200x1596.png 1272w, https://substackcdn.com/image/fetch/$s_!Wt7W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb3c7e5a-376b-459f-9fae-f3439e4e8d7a_3200x1596.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!Wt7W!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffb3c7e5a-376b-459f-9fae-f3439e4e8d7a_3200x1596.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Figura 2. O que é campo de protocolo e o que é conteúdo do payload. Compressão e particionamento acontecem fora do registro, no lote e no produtor.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Para quem não programa: o bloco abaixo é o código que publica um evento de liquidação. A primeira linha escolhe a chave — o identificador da conta — e é ela que garante a ordem; as duas seguintes penduram metadados de rastreio ao lado da mensagem.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;code&gt;ProducerRecord&amp;lt;String, SettlementEvent&amp;gt; record =
    new ProducerRecord&amp;lt;&amp;gt;(&amp;quot;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;http://settlement.events&quot;&gt;settlement.events&lt;/a&gt;&amp;quot;, accountId, event);

record.headers().add(&amp;quot;correlation-id&amp;quot;, correlationId.getBytes(UTF_8));
record.headers().add(&amp;quot;schema-version&amp;quot;, &amp;quot;v3&amp;quot;.getBytes(UTF_8));

producer.send(record);&lt;/code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Repare no que esse código &lt;em&gt;não&lt;/em&gt; faz: ele não espera resposta de ninguém. O produtor posta e segue a vida.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;Tópicos&lt;/h2&gt;&lt;p&gt;Tópicos são as categorias que organizam as mensagens: cada um agrupa o que trata do mesmo assunto.&lt;/p&gt;&lt;p&gt;Numa instituição financeira você teria um tópico para transações autorizadas, outro para lançamentos contábeis, outro para eventos de cadastro, outro para notificações ao cliente. Dentro de cada tópico, as mensagens ficam em ordem cronológica de gravação.&lt;/p&gt;&lt;p&gt;Tecnicamente, o tópico é onde os produtores publicam e os consumidores leem. E aqui está o detalhe que muda arquitetura: &lt;strong&gt;vários consumidores podem ler o mesmo tópico de forma simultânea e independente.&lt;/strong&gt; O motor de limites, o antifraude e o razão contábil leem exatamente os mesmos eventos de transação, cada um no seu ritmo, sem saber da existência um do outro e sem que o produtor precise ser alterado quando um quarto consumidor aparecer.&lt;/p&gt;&lt;p&gt;Isso é o que separa o Kafka de uma fila tradicional. Na fila clássica, quando alguém consome a mensagem, ela sai. No Kafka, ler não consome: a mensagem continua lá para todos os outros interessados. É um jornal, não uma carta — o fato de você ter lido não impede o vizinho de ler.&lt;/p&gt;&lt;p&gt;Para sistema financeiro, isso tem um nome: o tópico vira a fonte de verdade sobre o que aconteceu, e qualquer sistema novo pode ser ligado ao histórico sem precisar pedir nada a ninguém.&lt;/p&gt;&lt;p&gt;Uma recomendação de nomenclatura que paga dividendos no segundo ano: nomeie por domínio e por fato, no passado, com versão. &lt;code&gt;payments.pix.settled.v1&lt;/code&gt; diz o que carrega. &lt;code&gt;kafka-topic-2&lt;/code&gt; não diz nada, e você vai descobrir isso quando entrar o décimo quinto tópico do cluster.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;Partições&lt;/h2&gt;&lt;p&gt;Partição é como o Kafka divide o trabalho de um tópico. Milhões de mensagens numa única linha de produção demoram; em várias vias paralelas, não.&lt;/p&gt;&lt;p&gt;Antes da imagem certa, o aviso sobre a imagem errada, porque ela é comum: partição não é subcategoria do assunto. Todas as partições de um tópico guardam exatamente o mesmo tipo de mensagem — são vias paralelas, não gavetas temáticas. Quem acha o contrário cria um tópico pagamentos com uma partição para Pix, outra para boleto e outra para TED, e descobre tarde demais que não controla em qual partição a mensagem cai, nem consegue ler só uma delas por consumidor.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Então, para entender como a mensagem &lt;/strong&gt;&lt;em&gt;&lt;strong&gt;escolhe&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt; a partição, vale voltar ao correio.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Numa agência, a mesa de triagem divide a correspondência em corredores. A pessoa que faz a triagem não abre a carta: ela lê o CEP e aplica uma regra fixa. Mesmo CEP, sempre o mesmo corredor, todo dia, sem exceção. É isso que permite que o carteiro daquela rua receba tudo na sequência em que chegou.&lt;/p&gt;&lt;p&gt;A chave da mensagem é o CEP. O Kafka calcula um hash dela e divide pelo número de partições, e o resultado é determinístico: todos os eventos da conta &lt;code&gt;0001234-5&lt;/code&gt; caem no mesmo lugar e são processados na ordem em que entraram.&lt;/p&gt;&lt;p&gt;E a carta sem CEP? Aqui vem a terceira correção ao artigo antigo, e essa é sutil: &lt;strong&gt;a distribuição das mensagens sem chave não é aleatória.&lt;/strong&gt; Desde a 2.4 o produtor agrupa envios por partição em vez de alternar a cada mensagem, e desde a 3.3 o particionador embutido troca de partição a cada &lt;code&gt;batch.size&lt;/code&gt; de bytes enviados, escolhendo com mais frequência as partições cuja fila de envio está mais curta. &lt;strong&gt;A razão é a compressão de que falamos: lote cheio comprime melhor e custa menos rede. &lt;/strong&gt;Na prática, sem chave as suas mensagens saem em blocos, e o equilíbrio entre partições é consequência do tamanho das filas — não de um sorteio.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!Akk5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8862994-21bb-4edc-8e9d-664314280800_3200x1342.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!Akk5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8862994-21bb-4edc-8e9d-664314280800_3200x1342.png 424w, https://substackcdn.com/image/fetch/$s_!Akk5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8862994-21bb-4edc-8e9d-664314280800_3200x1342.png 848w, https://substackcdn.com/image/fetch/$s_!Akk5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8862994-21bb-4edc-8e9d-664314280800_3200x1342.png 1272w, https://substackcdn.com/image/fetch/$s_!Akk5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8862994-21bb-4edc-8e9d-664314280800_3200x1342.png 1456w&quot; sizes=&quot;auto, 100vw&quot;&gt;&lt;img width=&quot;1014&quot; height=&quot;425&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/a8862994-21bb-4edc-8e9d-664314280800_3200x1342.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:611,&amp;quot;width&amp;quot;:1456,&amp;quot;resizeWidth&amp;quot;:1014,&amp;quot;bytes&amp;quot;:300270,&amp;quot;alt&amp;quot;:&amp;quot;Figura 3. Com chave, a regra de triagem é fixa e a mesma chave cai sempre no mesmo lugar. Sem chave, a carta vai para o corredor com menos acúmulo no momento — não para um sorteado.&amp;quot;,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/220079580?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8862994-21bb-4edc-8e9d-664314280800_3200x1342.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt=&quot;Figura 3. Com chave, a regra de triagem é fixa e a mesma chave cai sempre no mesmo lugar. Sem chave, a carta vai para o corredor com menos acúmulo no momento — não para um sorteado.&quot; title=&quot;Figura 3. Com chave, a regra de triagem é fixa e a mesma chave cai sempre no mesmo lugar. Sem chave, a carta vai para o corredor com menos acúmulo no momento — não para um sorteado.&quot; data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!Akk5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8862994-21bb-4edc-8e9d-664314280800_3200x1342.png 424w, https://substackcdn.com/image/fetch/$s_!Akk5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8862994-21bb-4edc-8e9d-664314280800_3200x1342.png 848w, https://substackcdn.com/image/fetch/$s_!Akk5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8862994-21bb-4edc-8e9d-664314280800_3200x1342.png 1272w, https://substackcdn.com/image/fetch/$s_!Akk5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8862994-21bb-4edc-8e9d-664314280800_3200x1342.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!Akk5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8862994-21bb-4edc-8e9d-664314280800_3200x1342.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Figura 3. Com chave, a regra de triagem é fixa e a mesma chave cai sempre no mesmo lugar. Sem chave, a carta vai para o corredor com menos acúmulo no momento — não para um sorteado.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Guarde a consequência, que é a parte que importa: &lt;strong&gt;&lt;mark data-color=&quot;#fff2cc&quot; style=&quot;background-color: rgb(255, 242, 204); color: rgb(0, 0, 0)&quot;&gt;sem chave, não existe ordem&lt;/mark&gt;.&lt;/strong&gt; Não existe ordem parcial, não existe ordem “quase sempre”. Para o fluxo sem chave, a sequência em que as coisas aconteceram não está preservada em lugar nenhum.&lt;/p&gt;&lt;h3&gt;Por que o número de partições é quase irreversível&lt;/h3&gt;&lt;p&gt;Agora o ponto em que a analogia do correio quebra no mesmo lugar em que o sistema quebra — e por isso ela aguentou até aqui.&lt;/p&gt;&lt;p&gt;Se a agência abre corredores novos, a tabela de CEPs é recalculada: a rua que ia para o corredor 3 passa a ir para o 9. As cartas que já estavam no corredor 3 não se mudam sozinhas. Por um tempo, a correspondência daquela rua existe em dois corredores ao mesmo tempo, e ninguém consegue dizer qual carta chegou primeiro.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;É exatamente isso que acontece ao aumentar as partições de um tópico com chave.&lt;/strong&gt; O hash muda de destino, as mensagens antigas ficam onde estavam, e a ordem daquela conta deixa de existir durante a transição. Em tópico de saldo ou de lançamento, isso é um incidente, não um ajuste de capacidade.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!UWCP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdceba206-e7aa-4aea-9e6a-724df663b172_3200x1610.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!UWCP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdceba206-e7aa-4aea-9e6a-724df663b172_3200x1610.png 424w, https://substackcdn.com/image/fetch/$s_!UWCP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdceba206-e7aa-4aea-9e6a-724df663b172_3200x1610.png 848w, https://substackcdn.com/image/fetch/$s_!UWCP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdceba206-e7aa-4aea-9e6a-724df663b172_3200x1610.png 1272w, https://substackcdn.com/image/fetch/$s_!UWCP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdceba206-e7aa-4aea-9e6a-724df663b172_3200x1610.png 1456w&quot; sizes=&quot;auto, 100vw&quot;&gt;&lt;img width=&quot;944&quot; height=&quot;475&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/dceba206-e7aa-4aea-9e6a-724df663b172_3200x1610.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:733,&amp;quot;width&amp;quot;:1456,&amp;quot;resizeWidth&amp;quot;:944,&amp;quot;bytes&amp;quot;:288327,&amp;quot;alt&amp;quot;:&amp;quot;Figura 4. Dobrar o número de partições muda o destino do hash. Os eventos antigos ficam na partição antiga, os novos passam a cair em outra, e a ordem daquela conta deixa de existir.&amp;quot;,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/220079580?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdceba206-e7aa-4aea-9e6a-724df663b172_3200x1610.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt=&quot;Figura 4. Dobrar o número de partições muda o destino do hash. Os eventos antigos ficam na partição antiga, os novos passam a cair em outra, e a ordem daquela conta deixa de existir.&quot; title=&quot;Figura 4. Dobrar o número de partições muda o destino do hash. Os eventos antigos ficam na partição antiga, os novos passam a cair em outra, e a ordem daquela conta deixa de existir.&quot; data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!UWCP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdceba206-e7aa-4aea-9e6a-724df663b172_3200x1610.png 424w, https://substackcdn.com/image/fetch/$s_!UWCP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdceba206-e7aa-4aea-9e6a-724df663b172_3200x1610.png 848w, https://substackcdn.com/image/fetch/$s_!UWCP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdceba206-e7aa-4aea-9e6a-724df663b172_3200x1610.png 1272w, https://substackcdn.com/image/fetch/$s_!UWCP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdceba206-e7aa-4aea-9e6a-724df663b172_3200x1610.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!UWCP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdceba206-e7aa-4aea-9e6a-724df663b172_3200x1610.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Figura 4. Dobrar o número de partições muda o destino do hash. Os eventos antigos ficam na partição antiga, os novos passam a cair em outra, e a ordem daquela conta deixa de existir.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;A regra prática para quem dimensiona:&lt;strong&gt;&lt;mark data-color=&quot;#ffff00&quot; style=&quot;background-color: rgb(255, 255, 0); color: rgb(0, 0, 0)&quot;&gt; escolha o número de partições pelo paralelismo máximo que você acha que vai precisar nos próximos dois anos, e aceite uma folga&lt;/mark&gt;&lt;/strong&gt;. Partição demais custa memória, arquivos abertos e tempo de recuperação em falha; partição de menos custa um reparticionamento doloroso.&lt;/p&gt;&lt;p&gt;E tem um fator específico de banco, que é o corredor entupido: se o seu volume é desigual — um correspondente bancário que sozinho responde por 40% dos eventos — a chave por cliente vai criar uma partição quente enquanto as outras ficam ociosas. A solução é uma chave composta, do tipo identificador do cliente mais um sufixo pequeno, aceitando conscientemente que a ordem passa a valer por subconjunto e não pelo cliente inteiro.&lt;/p&gt;&lt;div data-attrs=&quot;{&amp;quot;url&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/subscribe?&amp;quot;,&amp;quot;text&amp;quot;:&amp;quot;Inscreva-se&amp;quot;,&amp;quot;language&amp;quot;:&amp;quot;pt-br&amp;quot;}&quot; data-component-name=&quot;SubscribeWidgetToDOM&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;p&gt;Assine gratuitamente para receber novos posts e apoiar meu trabalho.&lt;/p&gt;&lt;/div&gt;&lt;input tabindex=&quot;-1&quot; type=&quot;email&quot; name=&quot;article-fy8Zv1998hqRDif89gs5xY3LGkg-email&quot; placeholder=&quot;Digite seu e-mail…&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;O que são offsets&lt;/h2&gt;&lt;p&gt;Volte ao corredor da mesa de triagem. &lt;mark data-color=&quot;#fff2cc&quot; style=&quot;background-color: rgb(255, 242, 204); color: rgb(0, 0, 0)&quot;&gt;Cada carta que entra nele recebe uma etiqueta com número de série, na ordem de chegada: a primeira é a 0, a seguinte é a 1, e assim por diante, sem buraco e sem repetição.&lt;/mark&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;&lt;strong&gt;Essa etiqueta é o offset. &lt;/strong&gt;&lt;/em&gt;É ela que permite ao carteiro saber de onde pegar a próxima carta e — se for interrompido no meio do expediente — voltar exatamente ao ponto em que parou, sem recomeçar o corredor inteiro nem pular ninguém.&lt;/p&gt;&lt;p&gt;Então entenda  que &lt;strong&gt;o offset é a numeração única que o Kafka dá a cada mensagem dentro de uma partição&lt;/strong&gt;. Começa em zero e cresce a cada nova gravação. &lt;strong&gt;&lt;mark data-color=&quot;#fff2cc&quot; style=&quot;background-color: rgb(255, 242, 204); color: rgb(0, 0, 0)&quot;&gt;Ele é sequencial e imutável dentro da partição, e não tem relação nenhuma com os offsets das outras partições&lt;/mark&gt;&lt;/strong&gt;: a partição 0 pode estar no offset 9 enquanto a 1 está no 4.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Atenção a este ponto, porque é onde quase todo mundo erra:&lt;/strong&gt; &lt;mark data-color=&quot;#fff2cc&quot; style=&quot;background-color: rgb(255, 242, 204); color: rgb(0, 0, 0)&quot;&gt;o offset não guarda a mensagem. Ele é o número da página, não a página. E, mais importante, existem dois offsets diferentes na sua vida, que o artigo antigo tratava como se fossem um só:&lt;/mark&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;O offset da mensagem&lt;/strong&gt; é o endereço dela no log. É do Kafka, imutável, e existe desde que ela foi gravada.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;O offset commitado do grupo&lt;/strong&gt; é um marcador separado que diz “este grupo de consumidores já processou até aqui”. Esse é gravado pelo &lt;em&gt;consumidor&lt;/em&gt;, num tópico interno chamado &lt;code&gt;__consumer_offsets&lt;/code&gt;, e só quando o consumidor manda gravar.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;A distância entre esses dois é onde mora o incidente do Rafael que vimos na introdução do artigo. E é por isso que a próxima seção existe.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!ZVg5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0ff9e91-12d5-4a10-a283-a3a214d52add_3200x1292.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!ZVg5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0ff9e91-12d5-4a10-a283-a3a214d52add_3200x1292.png 424w, https://substackcdn.com/image/fetch/$s_!ZVg5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0ff9e91-12d5-4a10-a283-a3a214d52add_3200x1292.png 848w, https://substackcdn.com/image/fetch/$s_!ZVg5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0ff9e91-12d5-4a10-a283-a3a214d52add_3200x1292.png 1272w, https://substackcdn.com/image/fetch/$s_!ZVg5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0ff9e91-12d5-4a10-a283-a3a214d52add_3200x1292.png 1456w&quot; sizes=&quot;auto, 100vw&quot;&gt;&lt;img width=&quot;1004&quot; height=&quot;405&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/a0ff9e91-12d5-4a10-a283-a3a214d52add_3200x1292.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:588,&amp;quot;width&amp;quot;:1456,&amp;quot;resizeWidth&amp;quot;:1004,&amp;quot;bytes&amp;quot;:206662,&amp;quot;alt&amp;quot;:&amp;quot;Figura 5. O offset da mensagem é do log e nasce com ela. O offset commitado é do grupo e só existe quando o consumidor decide gravá-lo — a distância entre os dois é o que será reprocessado numa falha.&amp;quot;,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/220079580?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0ff9e91-12d5-4a10-a283-a3a214d52add_3200x1292.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt=&quot;Figura 5. O offset da mensagem é do log e nasce com ela. O offset commitado é do grupo e só existe quando o consumidor decide gravá-lo — a distância entre os dois é o que será reprocessado numa falha.&quot; title=&quot;Figura 5. O offset da mensagem é do log e nasce com ela. O offset commitado é do grupo e só existe quando o consumidor decide gravá-lo — a distância entre os dois é o que será reprocessado numa falha.&quot; data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!ZVg5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0ff9e91-12d5-4a10-a283-a3a214d52add_3200x1292.png 424w, https://substackcdn.com/image/fetch/$s_!ZVg5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0ff9e91-12d5-4a10-a283-a3a214d52add_3200x1292.png 848w, https://substackcdn.com/image/fetch/$s_!ZVg5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0ff9e91-12d5-4a10-a283-a3a214d52add_3200x1292.png 1272w, https://substackcdn.com/image/fetch/$s_!ZVg5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0ff9e91-12d5-4a10-a283-a3a214d52add_3200x1292.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!ZVg5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa0ff9e91-12d5-4a10-a283-a3a214d52add_3200x1292.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Figura 5. O offset da mensagem é do log e nasce com ela. O offset commitado é do grupo e só existe quando o consumidor decide gravá-lo — a distância entre os dois é o que será reprocessado numa falha.       &lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Pense comigo&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Quais dos seus tópicos dependem de ordem, e todos eles estão publicando com chave hoje?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Se você precisasse dobrar o número de partições de um tópico de saldo amanhã, qual seria o plano?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Alguém no seu time consegue dizer, sem olhar o código, se os consumidores commitam antes ou depois de processar?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;Grupos de consumidores e o limite do paralelismo&lt;/h2&gt;&lt;p&gt;Consumidores se inscrevem em &lt;strong&gt;tópicos&lt;/strong&gt;, não em partições. Quando vários consumidores declaram o mesmo &lt;code&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;http://group.id&quot;&gt;group.id&lt;/a&gt;&lt;/code&gt;, o Kafka os trata como um time e distribui as partições entre eles.&lt;/p&gt;&lt;p&gt;Volte ao correio: é a equipe de carteiros de uma agência. O bairro é o tópico, as ruas são as partições, e o chefe da agência distribui as ruas entre os carteiros que apareceram para trabalhar. Cada rua tem exatamente um carteiro responsável naquele dia.&lt;/p&gt;&lt;p&gt;Dessa imagem sai o limite que mais pega gente desprevenida: &lt;strong&gt;uma partição nunca é atribuída a dois consumidores do mesmo grupo.&lt;/strong&gt; Logo, o paralelismo máximo de um grupo é o número de partições. Seis partições e dez consumidores significam seis trabalhando e quatro parados, queimando memória e custo sem processar nada. Escalar réplicas no Kubernetes depois desse ponto não acelera coisa nenhuma — e esse é o diagnóstico de uma boa parte dos chamados de “o consumidor está lento”.&lt;/p&gt;&lt;p&gt;Quando um consumidor entra ou sai do grupo, acontece um &lt;em&gt;rebalance&lt;/em&gt;: as ruas são redistribuídas. Historicamente isso era caro, porque o protocolo antigo parava o grupo inteiro para redistribuir — o famoso “stop-the-world”.&lt;/p&gt;&lt;p&gt;Isso mudou. O Kafka 4.0 trouxe a disponibilidade geral da KIP-848, a nova geração do protocolo de rebalanceamento de consumidores, que despede os rebalances que param o mundo e aumenta a estabilidade e a performance dos grupos, além de simplificar os clientes. O protocolo novo já vem habilitado por padrão no servidor, mas os consumidores precisam optar por ele explicitamente, configurando &lt;code&gt;group.protocol=consumer&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Para quem não programa: a configuração abaixo liga o protocolo novo de coordenação de grupo no seu consumidor. Sem essa linha, você está rodando um Kafka 4 com o comportamento de rebalanceamento antigo.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;code&gt;group.id=settlement-writer
group.protocol=consumer
&lt;/code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Se você opera grupos grandes, essa única linha costuma ser o maior ganho de estabilidade disponível na migração para a linha 4.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!B13H!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb78b258-24e5-4ea8-bb3c-5f36126d800e_3200x1456.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!B13H!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb78b258-24e5-4ea8-bb3c-5f36126d800e_3200x1456.png 424w, https://substackcdn.com/image/fetch/$s_!B13H!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb78b258-24e5-4ea8-bb3c-5f36126d800e_3200x1456.png 848w, https://substackcdn.com/image/fetch/$s_!B13H!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb78b258-24e5-4ea8-bb3c-5f36126d800e_3200x1456.png 1272w, https://substackcdn.com/image/fetch/$s_!B13H!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb78b258-24e5-4ea8-bb3c-5f36126d800e_3200x1456.png 1456w&quot; sizes=&quot;auto, 100vw&quot;&gt;&lt;img width=&quot;856&quot; height=&quot;389&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/cb78b258-24e5-4ea8-bb3c-5f36126d800e_3200x1456.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:662,&amp;quot;width&amp;quot;:1456,&amp;quot;resizeWidth&amp;quot;:856,&amp;quot;bytes&amp;quot;:260546,&amp;quot;alt&amp;quot;:&amp;quot;Figura 6. O paralelismo de um grupo para no número de partições. Consumidores além desse número ficam ociosos, e aumentar réplicas não resolve lentidão.&amp;quot;,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/220079580?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb78b258-24e5-4ea8-bb3c-5f36126d800e_3200x1456.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt=&quot;Figura 6. O paralelismo de um grupo para no número de partições. Consumidores além desse número ficam ociosos, e aumentar réplicas não resolve lentidão.&quot; title=&quot;Figura 6. O paralelismo de um grupo para no número de partições. Consumidores além desse número ficam ociosos, e aumentar réplicas não resolve lentidão.&quot; data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!B13H!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb78b258-24e5-4ea8-bb3c-5f36126d800e_3200x1456.png 424w, https://substackcdn.com/image/fetch/$s_!B13H!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb78b258-24e5-4ea8-bb3c-5f36126d800e_3200x1456.png 848w, https://substackcdn.com/image/fetch/$s_!B13H!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb78b258-24e5-4ea8-bb3c-5f36126d800e_3200x1456.png 1272w, https://substackcdn.com/image/fetch/$s_!B13H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb78b258-24e5-4ea8-bb3c-5f36126d800e_3200x1456.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!B13H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb78b258-24e5-4ea8-bb3c-5f36126d800e_3200x1456.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Figura 6. O paralelismo de um grupo para no número de partições. Consumidores além desse número ficam ociosos, e aumentar réplicas não resolve lentidão.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;As três garantias que você acha que tem&lt;/h2&gt;&lt;p&gt;Três frases aparecem em quase toda explicação de Kafka por aí, inclusive na minha antiga. As três são falsas, e em sistema financeiro as três custam caro.&lt;/p&gt;&lt;h3&gt;“Cada mensagem é entregue uma vez só”&lt;/h3&gt;&lt;p&gt;O padrão do Kafka é &lt;strong&gt;at-least-once&lt;/strong&gt;: pelo menos uma vez. O consumidor lê, processa, e depois informa até onde chegou. Se ele morre entre processar e informar, o substituto recomeça do último ponto confirmado e reprocessa. Não é bug — é o contrato.&lt;/p&gt;&lt;p&gt;É o motoboy que entrega o pedido e só dá baixa no aplicativo quando volta para a moto. Se o celular descarrega no meio, o sistema ainda acha que o pedido está em rota, e manda outro motoboy.&lt;/p&gt;&lt;p&gt;Você tem três opções, e precisa escolher conscientemente:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Commitar antes de processar&lt;/strong&gt; te dá &lt;em&gt;at-most-once&lt;/em&gt;: no máximo uma vez. Nunca duplica, e perde quando falha. Em telemetria, aceitável. Em liquidação, inaceitável — perder lançamento é pior que duplicar, porque duplicata aparece na conciliação e sumiço não aparece em lugar nenhum.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Commitar depois de processar&lt;/strong&gt; te dá &lt;em&gt;at-least-once&lt;/em&gt;: nunca perde, e duplica quando falha. É o padrão certo para dinheiro, e é o que o Rafael tinha. O preço é que &lt;strong&gt;todo consumidor precisa ser idempotente&lt;/strong&gt; — processar duas vezes tem que dar o mesmo resultado que processar uma. Essa é a conta que ninguém paga no dia do deploy e todo mundo paga no dia do incidente, e é o assunto que eu já tratei por inteiro no artigo sobre idempotência da série.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usar transações do Kafka&lt;/strong&gt; te dá &lt;em&gt;exactly-once&lt;/em&gt; dentro do Kafka. A ressalva está na última palavra. Se o seu consumidor lê do Kafka, grava no Postgres e publica noutro tópico, a transação do Kafka cobre a leitura e a publicação, mas não cobre o Postgres. Para o caminho que atravessa o banco de dados, a resposta continua sendo idempotência no seu lado.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!qa8b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b942f1-46ea-4200-a0c8-2950cad42618_3200x1598.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!qa8b!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b942f1-46ea-4200-a0c8-2950cad42618_3200x1598.png 424w, https://substackcdn.com/image/fetch/$s_!qa8b!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b942f1-46ea-4200-a0c8-2950cad42618_3200x1598.png 848w, https://substackcdn.com/image/fetch/$s_!qa8b!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b942f1-46ea-4200-a0c8-2950cad42618_3200x1598.png 1272w, https://substackcdn.com/image/fetch/$s_!qa8b!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b942f1-46ea-4200-a0c8-2950cad42618_3200x1598.png 1456w&quot; sizes=&quot;auto, 100vw&quot;&gt;&lt;img width=&quot;952&quot; height=&quot;475&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/72b942f1-46ea-4200-a0c8-2950cad42618_3200x1598.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:727,&amp;quot;width&amp;quot;:1456,&amp;quot;resizeWidth&amp;quot;:952,&amp;quot;bytes&amp;quot;:343408,&amp;quot;alt&amp;quot;:&amp;quot;Figura 7. A mesma falha, dois momentos de commit, dois desfechos. Em sistema financeiro, duplicar é recuperável e perder não é — por isso o padrão é commitar depois e construir idempotência.&amp;quot;,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/220079580?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b942f1-46ea-4200-a0c8-2950cad42618_3200x1598.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt=&quot;Figura 7. A mesma falha, dois momentos de commit, dois desfechos. Em sistema financeiro, duplicar é recuperável e perder não é — por isso o padrão é commitar depois e construir idempotência.&quot; title=&quot;Figura 7. A mesma falha, dois momentos de commit, dois desfechos. Em sistema financeiro, duplicar é recuperável e perder não é — por isso o padrão é commitar depois e construir idempotência.&quot; data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!qa8b!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b942f1-46ea-4200-a0c8-2950cad42618_3200x1598.png 424w, https://substackcdn.com/image/fetch/$s_!qa8b!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b942f1-46ea-4200-a0c8-2950cad42618_3200x1598.png 848w, https://substackcdn.com/image/fetch/$s_!qa8b!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b942f1-46ea-4200-a0c8-2950cad42618_3200x1598.png 1272w, https://substackcdn.com/image/fetch/$s_!qa8b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b942f1-46ea-4200-a0c8-2950cad42618_3200x1598.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!qa8b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72b942f1-46ea-4200-a0c8-2950cad42618_3200x1598.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Figura 7. A mesma falha, dois momentos de commit, dois desfechos. Em sistema financeiro, duplicar é recuperável e perder não é — por isso o padrão é commitar depois e construir idempotência.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;h3&gt;“O Kafka guarda a mensagem até alguém consumir”&lt;/h3&gt;&lt;p&gt;Não guarda. A retenção é por tempo ou por tamanho, e o apagamento acontece independentemente de alguém ter lido.&lt;/p&gt;&lt;p&gt;É o guarda-volumes do supermercado: esvazia no fim do expediente, e não pergunta se você voltou. Um consumidor fora do ar além da janela de retenção não volta atrasado — ele volta e descobre que o ponto onde parou não existe mais, recebe um &lt;code&gt;OFFSET_OUT_OF_RANGE&lt;/code&gt;, e aí o comportamento depende de um parâmetro que quase ninguém revisa: &lt;code&gt;auto.offset.reset&lt;/code&gt;. Em &lt;code&gt;latest&lt;/code&gt;, ele pula silenciosamente todo o buraco e você perde dado sem nenhum erro. Em &lt;code&gt;earliest&lt;/code&gt;, ele reprocessa o tópico inteiro desde o começo, o que num tópico de pagamento é um tipo diferente de desastre.&lt;/p&gt;&lt;p&gt;O padrão de sete dias é confortável até o feriadão em que o time inteiro está fora e um consumidor fica três dias em &lt;code&gt;CrashLoopBackOff&lt;/code&gt;. Para tópicos financeiros, retenção generosa é barata perto do custo de descobrir isso em produção.&lt;/p&gt;&lt;p&gt;Existe ainda a compactação de log, que é outro regime: em vez de apagar por idade, o Kafka mantém o último valor de cada chave para sempre. É o que você quer para um tópico de estado — cadastro de cliente, limites vigentes, tabela de tarifas — em que o histórico não importa, mas o valor atual precisa estar sempre disponível para quem subir amanhã.&lt;/p&gt;&lt;h3&gt;“As mensagens chegam na ordem”&lt;/h3&gt;&lt;p&gt;Dentro de uma partição, sim. Entre partições, nunca. Como a partição é escolhida pela chave, ordem só existe para o que compartilha chave.&lt;/p&gt;&lt;p&gt;Volte aos corredores: dentro de um, as etiquetas são 0, 1, 2 e ninguém discute a sequência. Entre dois corredores diferentes, não existe etiqueta comum nenhuma — e nenhum funcionário da agência consegue dizer qual das duas cartas chegou primeiro.&lt;/p&gt;&lt;p&gt;Há ainda uma forma sutil de perder ordem dentro da própria partição: sem o produtor idempotente ligado, uma mensagem que falha e é reenviada pode ultrapassar outra que já estava em trânsito. Ligue &lt;code&gt;enable.idempotence=true&lt;/code&gt; e esse problema deixa de existir — é o padrão desde a 3.0, mas vale conferir no seu cliente, porque muitos times carregam configuração herdada de antes disso.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Pense comigo&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Qual é a retenção dos seus tópicos críticos, e ela cobre o seu maior feriado do ano?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Se o consumidor de hoje reprocessar o lote de ontem inteiro agora, o que quebra?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Seus consumidores têm chave de idempotência persistida, ou dependem de “não vai acontecer duas vezes”?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;Onde a durabilidade realmente mora&lt;/h2&gt;&lt;p&gt;No artigo anterior eu escrevi que o ZooKeeper era “o verdadeiro herói nos bastidores” da resiliência do Kafka. Aqui eu preciso ser honesto com o meu próprio texto: isso estava errado, e continuaria errado mesmo que o ZooKeeper nunca tivesse saído do produto.&lt;/p&gt;&lt;p&gt;A coordenação do cluster responde &lt;em&gt;quem&lt;/em&gt; é o dono de cada partição. Ela não responde &lt;em&gt;quantas cópias do seu dado existem antes de o broker dizer “gravei”&lt;/em&gt;. A segunda pergunta é a que decide se você perde dinheiro, e a resposta dela está em três configurações que trabalham juntas.&lt;/p&gt;&lt;p&gt;Pense num cheque que só é aceito com duas firmas reconhecidas. Uma assinatura é mais rápida e não vale nada se o cartório pegar fogo. A exigência da segunda não é burocracia — é a diferença entre um registro e uma promessa.&lt;/p&gt;&lt;p&gt;Para quem não programa: o bloco abaixo diz ao Kafka “mantenha três cópias desse dado, só me confirme a gravação depois que pelo menos duas delas estiverem em dia, e se não houver duas disponíveis, recuse a escrita em vez de aceitar e torcer”.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;code&gt;# no tópico, definido na criação (mudar depois exige reatribuição de partições)
--replication-factor 3

# no tópico, alterável depois
min.insync.replicas=2

# no produtor — padrão desde a versão 3.0, mas confirme em vez de presumir
acks=all
enable.idempotence=true
&lt;/code&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Repare no comentário da última seção do bloco, porque ele muda o alvo da sua atenção: desde a versão 3.0, &lt;code&gt;acks=all&lt;/code&gt; e &lt;code&gt;enable.idempotence=true&lt;/code&gt; &lt;strong&gt;já são o padrão do produtor&lt;/strong&gt;. O que derruba cluster hoje raramente é esquecer de ligá-los — é herdar um cliente configurado em 2019 que os sobrescreve, ou deixar &lt;code&gt;min.insync.replicas&lt;/code&gt; no padrão, que é 1.&lt;/p&gt;&lt;p&gt;E é aí que mora a armadilha: &lt;code&gt;acks=all&lt;/code&gt; com &lt;code&gt;min.insync.replicas=1&lt;/code&gt; é teatro. Se sobrar uma única réplica viva, “todas as réplicas em dia” passa a significar “uma”, e o broker confirma alegremente uma escrita que some no próximo reinício. Replicação 3 com mínimo 2 tolera a perda de um broker sem perder escrita nem durabilidade — e recusa a escrita quando não dá para garantir, que é exatamente o comportamento que você quer em liquidação.&lt;/p&gt;&lt;p&gt;O conceito por trás disso é o &lt;strong&gt;ISR&lt;/strong&gt;, o conjunto de réplicas em dia com o líder. Uma réplica que ficou para trás sai do conjunto, e uma réplica fora do conjunto não conta para o mínimo. Daí decorre o parâmetro mais perigoso do Kafka: &lt;code&gt;unclean.leader.election.enable&lt;/code&gt;. Ligado, ele permite que uma réplica atrasada assuma a liderança quando todas as atualizadas morrerem — o cluster volta mais rápido e &lt;strong&gt;perde&lt;/strong&gt; os dados que só existiam nas réplicas em dia. Em sistema financeiro, ele fica desligado, e a decisão de ligar é de negócio, não de plantão.&lt;/p&gt;&lt;p&gt;A contrapartida é honesta e precisa ser dita: &lt;code&gt;acks=all&lt;/code&gt; custa latência, porque a confirmação espera a replicação. Em fluxo de alto volume e baixo valor unitário, talvez você aceite &lt;code&gt;acks=1&lt;/code&gt;. Em liquidação, não. Essa é uma escolha de risco, não de performance — e deveria estar escrita em algum lugar além do arquivo de configuração.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!QVDx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc579a005-c65e-4bff-b651-6cf4aa211c99_3200x1420.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!QVDx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc579a005-c65e-4bff-b651-6cf4aa211c99_3200x1420.png 424w, https://substackcdn.com/image/fetch/$s_!QVDx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc579a005-c65e-4bff-b651-6cf4aa211c99_3200x1420.png 848w, https://substackcdn.com/image/fetch/$s_!QVDx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc579a005-c65e-4bff-b651-6cf4aa211c99_3200x1420.png 1272w, https://substackcdn.com/image/fetch/$s_!QVDx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc579a005-c65e-4bff-b651-6cf4aa211c99_3200x1420.png 1456w&quot; sizes=&quot;auto, 100vw&quot;&gt;&lt;img width=&quot;994&quot; height=&quot;441&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/c579a005-c65e-4bff-b651-6cf4aa211c99_3200x1420.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:646,&amp;quot;width&amp;quot;:1456,&amp;quot;resizeWidth&amp;quot;:994,&amp;quot;bytes&amp;quot;:280118,&amp;quot;alt&amp;quot;:&amp;quot;Figura 8. Fator de replicação, mínimo de réplicas em dia e nível de confirmação trabalham juntos. Com acks=all sozinho, “todas” pode significar “uma” — e aí a confirmação não garante nada.&amp;quot;,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/220079580?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc579a005-c65e-4bff-b651-6cf4aa211c99_3200x1420.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt=&quot;Figura 8. Fator de replicação, mínimo de réplicas em dia e nível de confirmação trabalham juntos. Com acks=all sozinho, “todas” pode significar “uma” — e aí a confirmação não garante nada.&quot; title=&quot;Figura 8. Fator de replicação, mínimo de réplicas em dia e nível de confirmação trabalham juntos. Com acks=all sozinho, “todas” pode significar “uma” — e aí a confirmação não garante nada.&quot; data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!QVDx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc579a005-c65e-4bff-b651-6cf4aa211c99_3200x1420.png 424w, https://substackcdn.com/image/fetch/$s_!QVDx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc579a005-c65e-4bff-b651-6cf4aa211c99_3200x1420.png 848w, https://substackcdn.com/image/fetch/$s_!QVDx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc579a005-c65e-4bff-b651-6cf4aa211c99_3200x1420.png 1272w, https://substackcdn.com/image/fetch/$s_!QVDx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc579a005-c65e-4bff-b651-6cf4aa211c99_3200x1420.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!QVDx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc579a005-c65e-4bff-b651-6cf4aa211c99_3200x1420.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Figura 8. Fator de replicação, mínimo de réplicas em dia e nível de confirmação trabalham juntos. Com acks=all sozinho, “todas” pode significar “uma” — e aí a confirmação não garante nada.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Pense comigo&lt;/strong&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Qual é o min.insync.replicas dos seus tópicos de dinheiro hoje, confirmado no cluster e não no arquivo de configuração do repositório?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Se um broker cair agora, os seus tópicos críticos continuam aceitando escrita sem abrir mão de durabilidade?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;unclean.leader.election.enable está desligado nos tópicos de liquidação, e existe alguém com autoridade de negócio para decidir ligá-lo durante um incidente?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/blockquote&gt;&lt;h2&gt;O Kafka mudou de anatomia: o fim do ZooKeeper&lt;/h2&gt;&lt;p&gt;Se você aprendeu Kafka em 2022, a figura na sua cabeça tem um componente que não existe mais.&lt;/p&gt;&lt;p&gt;Durante mais de dez anos, o Kafka terceirizou o consenso do cluster — quem está vivo, quem é dono de qual partição, quem assume quando o dono morre — para um cluster separado, o Apache ZooKeeper. Era um prédio que contrata uma administradora externa para tudo que exige decisão coletiva. Funciona, mas são duas empresas para manter, dois monitoramentos, dois plantões, e quando a administradora cai o prédio não sabe mais quem manda em quê.&lt;/p&gt;&lt;p&gt;O Kafka 4.0, lançado em março de 2025, é o primeiro release major a operar inteiramente sem o ZooKeeper, rodando em modo KRaft por padrão — o que elimina a complexidade de manter um ensemble separado, reduz a carga operacional e simplifica a administração. O projeto fez questão de agradecer na nota de lançamento: o ZooKeeper foi a espinha dorsal do Kafka por mais de uma década.&lt;/p&gt;&lt;p&gt;O condomínio passou a eleger síndico entre os próprios moradores. Alguns brokers assumem o papel de &lt;em&gt;controller&lt;/em&gt;, formam um quórum interno, guardam a ata das decisões num log próprio e elegem um líder entre si.&lt;/p&gt;&lt;p&gt;E aqui eu preciso estragar um pouco a minha própria analogia, senão quem opera cluster para de confiar no resto do artigo: num condomínio, se ninguém aparecer na assembleia, a vida continua. Num quórum KRaft, não. A eleição exige maioria dos controllers vivos — três toleram a perda de um, cinco toleram dois. Perdido o quórum, o cluster congela as mudanças de metadados. A analogia quebra exatamente no ponto em que o sistema quebra, que é o único lugar onde ela precisava aguentar.&lt;/p&gt;&lt;p&gt;A versão atual da linha é a 4.3, de maio de 2026. Se o seu cluster ainda roda 3.x com ZooKeeper, você está operando uma arquitetura que saiu do produto, e o caminho de migração passa obrigatoriamente pela 3.9. Vale uma observação de calendário para quem planeja orçamento: migração de coordenação não é janela de uma noite, e é o tipo de trabalho que some do roadmap até virar urgência.&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://substackcdn.com/image/fetch/$s_!YJOD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c2927a5-b031-4b79-b644-46c6f219d03b_3200x1508.png&quot; data-component-name=&quot;Image2ToDOM&quot;&gt;&lt;div&gt;&lt;picture&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://substackcdn.com/image/fetch/$s_!YJOD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c2927a5-b031-4b79-b644-46c6f219d03b_3200x1508.png 424w, https://substackcdn.com/image/fetch/$s_!YJOD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c2927a5-b031-4b79-b644-46c6f219d03b_3200x1508.png 848w, https://substackcdn.com/image/fetch/$s_!YJOD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c2927a5-b031-4b79-b644-46c6f219d03b_3200x1508.png 1272w, https://substackcdn.com/image/fetch/$s_!YJOD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c2927a5-b031-4b79-b644-46c6f219d03b_3200x1508.png 1456w&quot; sizes=&quot;auto, 100vw&quot;&gt;&lt;img width=&quot;940&quot; height=&quot;442&quot; data-attrs=&quot;{&amp;quot;src&amp;quot;:&amp;quot;https://substack-post-media.s3.amazonaws.com/public/images/6c2927a5-b031-4b79-b644-46c6f219d03b_3200x1508.png&amp;quot;,&amp;quot;srcNoWatermark&amp;quot;:null,&amp;quot;fullscreen&amp;quot;:false,&amp;quot;imageSize&amp;quot;:&amp;quot;large&amp;quot;,&amp;quot;height&amp;quot;:686,&amp;quot;width&amp;quot;:1456,&amp;quot;resizeWidth&amp;quot;:940,&amp;quot;bytes&amp;quot;:274811,&amp;quot;alt&amp;quot;:&amp;quot;Figura 9. Coordenação externa contra quórum interno, com a mesma falha aplicada aos dois. O que mudou foi quem decide — não o que garante a durabilidade do dado.&amp;quot;,&amp;quot;title&amp;quot;:null,&amp;quot;type&amp;quot;:&amp;quot;image/png&amp;quot;,&amp;quot;href&amp;quot;:null,&amp;quot;belowTheFold&amp;quot;:true,&amp;quot;topImage&amp;quot;:false,&amp;quot;internalRedirect&amp;quot;:&amp;quot;https://chroniclesofapragmaticprogrammer.substack.com/i/220079580?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c2927a5-b031-4b79-b644-46c6f219d03b_3200x1508.png&amp;quot;,&amp;quot;isProcessing&amp;quot;:false,&amp;quot;align&amp;quot;:&amp;quot;center&amp;quot;,&amp;quot;offset&amp;quot;:false}&quot; alt=&quot;Figura 9. Coordenação externa contra quórum interno, com a mesma falha aplicada aos dois. O que mudou foi quem decide — não o que garante a durabilidade do dado.&quot; title=&quot;Figura 9. Coordenação externa contra quórum interno, com a mesma falha aplicada aos dois. O que mudou foi quem decide — não o que garante a durabilidade do dado.&quot; data-orig-srcset=&quot;https://substackcdn.com/image/fetch/$s_!YJOD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c2927a5-b031-4b79-b644-46c6f219d03b_3200x1508.png 424w, https://substackcdn.com/image/fetch/$s_!YJOD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c2927a5-b031-4b79-b644-46c6f219d03b_3200x1508.png 848w, https://substackcdn.com/image/fetch/$s_!YJOD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c2927a5-b031-4b79-b644-46c6f219d03b_3200x1508.png 1272w, https://substackcdn.com/image/fetch/$s_!YJOD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c2927a5-b031-4b79-b644-46c6f219d03b_3200x1508.png 1456w&quot; src=&quot;https://substackcdn.com/image/fetch/$s_!YJOD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c2927a5-b031-4b79-b644-46c6f219d03b_3200x1508.png&quot;&gt;&lt;/picture&gt;&lt;div&gt;&lt;div&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 20 20&quot; fill=&quot;none&quot; stroke-width=&quot;1.5&quot; stroke=&quot;var(--color-fg-primary)&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;&lt;g&gt;&lt;path d=&quot;M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882&quot;&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/button&gt;&lt;button tabindex=&quot;-1&quot; type=&quot;button&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;20&quot; height=&quot;20&quot; viewbox=&quot;0 0 24 24&quot; fill=&quot;none&quot; stroke=&quot;currentColor&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot;&gt;&lt;polyline points=&quot;15 3 21 3 21 9&quot;&gt;&lt;/polyline&gt;&lt;polyline points=&quot;9 21 3 21 3 15&quot;&gt;&lt;/polyline&gt;&lt;line x1=&quot;21&quot; x2=&quot;14&quot; y1=&quot;3&quot; y2=&quot;10&quot;&gt;&lt;/line&gt;&lt;line x1=&quot;3&quot; x2=&quot;10&quot; y1=&quot;21&quot; y2=&quot;14&quot;&gt;&lt;/line&gt;&lt;/svg&gt;&lt;/button&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&lt;figcaption&gt;Figura 9. Coordenação externa contra quórum interno, com a mesma falha aplicada aos dois. O que mudou foi quem decide — não o que garante a durabilidade do dado.&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;Filas nativas: a pergunta que ficou em aberto&lt;/h2&gt;&lt;p&gt;No artigo anterior eu levantei uma dúvida e não fechei: se só existe um consumidor interessado, Kafka ou RabbitMQ? Na época a resposta honesta era “depende do volume”. Mudou.&lt;/p&gt;&lt;p&gt;O Kafka 4.2, de fevereiro de 2026, tornou as Kafka Queues — os &lt;em&gt;share groups&lt;/em&gt; — prontas para produção. São grupos em que vários consumidores processam registros das mesmas partições concorrentemente, com reconhecimento individual e contagem de entregas, habilitando casos de uso de fila sem a amarração estrita de uma partição por consumidor. O caminho foi longo: entrou como acesso antecipado na 4.0, virou preview na 4.1 e só agora ficou pronto.&lt;/p&gt;&lt;p&gt;Pense num mutirão de correção de provas. A pilha é uma só, cada professor pega a próxima prova livre, e a prova corrigida sai da pilha. Se um professor sai no meio da correção, a prova volta para a pilha e outro pega. Ninguém é dono de um bloco inteiro, e por isso um professor lento não trava os outros.&lt;/p&gt;&lt;p&gt;Isso resolve justamente o limite da Figura 6. Com share groups, o número de consumidores deixa de estar preso ao número de partições, e você escala o processamento sem reparticionar o tópico — o que, como vimos, é uma operação que você não quer fazer num tópico com chave.&lt;/p&gt;&lt;p&gt;A 4.2 ainda trouxe o reconhecimento do tipo &lt;code&gt;RENEW&lt;/code&gt;, que permite estender o prazo de posse de um registro que demora a processar, e métricas de lag por partição compartilhada. São as duas coisas que faltavam para isso virar algo que se coloca em produção sem medo: a primeira resolve o registro que exige uma chamada externa lenta, a segunda resolve o “como eu monitoro isso”.&lt;/p&gt;&lt;p&gt;Onde isso encaixa em banco: processamento de documentos, chamadas a birôs externos, filas de análise manual — tudo que tem trabalho desigual por item e não depende de ordem. Onde &lt;strong&gt;não&lt;/strong&gt; encaixa: qualquer fluxo em que a ordem por conta importa, porque share group abre mão exatamente dela.&lt;/p&gt;&lt;p&gt;A resposta nova para a pergunta antiga, então, é que a conversa mudou de “qual ferramenta” para “qual modelo de consumo dentro da mesma ferramenta”.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;Quando o Kafka não é a resposta&lt;/h2&gt;&lt;p&gt;Um artigo que só elogia a ferramenta não ajuda ninguém a decidir. O Kafka custa: um cluster para operar, um ecossistema para aprender, um modelo mental que o time inteiro precisa dominar, e uma classe nova de incidentes que não existia antes.&lt;/p&gt;&lt;p&gt;Ele é a escolha errada quando você precisa de resposta síncrona — se o chamador precisa do resultado para continuar, isso é uma chamada, não um evento, e empurrar isso para um tópico só adiciona latência e complexidade. É escolha duvidosa quando o volume é baixo e o roteamento é complexo, porque é aí que um broker com roteamento rico resolve melhor. E é escolha ruim quando ninguém no time tem experiência de operação e não há plano para adquirir — Kafka mal operado em sistema financeiro é pior que não ter Kafka.&lt;/p&gt;&lt;p&gt;A pergunta útil não é “Kafka é bom?”. É: &lt;em&gt;eu preciso que mais de um sistema reaja a esse fato, no tempo deles, com direito a reler o histórico?&lt;/em&gt; Se sim, é Kafka. Se não, provavelmente é outra coisa.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;Checklist para quem roda Kafka com dinheiro em cima&lt;/h2&gt;&lt;p&gt;Se você levar uma só coisa deste artigo para a segunda-feira, que seja esta lista. Ela é curta de propósito:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chave em todo tópico que depende de ordem.&lt;/strong&gt; Sem chave, não há ordem — e isso falha em silêncio.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;acks=all&lt;/code&gt;&lt;strong&gt; com &lt;/strong&gt;&lt;code&gt;min.insync.replicas=2&lt;/code&gt;&lt;strong&gt; e replicação 3&lt;/strong&gt; nos tópicos de dinheiro. As três juntas, ou nenhuma.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;unclean.leader.election.enable=false&lt;/code&gt;&lt;strong&gt;.&lt;/strong&gt; Voltar rápido perdendo lançamento não é voltar.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Commit depois de processar, com consumidor idempotente.&lt;/strong&gt; Escreva a chave de idempotência no banco, na mesma transação do efeito.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Retenção que cubra o seu pior cenário de indisponibilidade&lt;/strong&gt;, e &lt;code&gt;auto.offset.reset&lt;/code&gt; revisado conscientemente — não no padrão por inércia.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Alerta de lag por partição&lt;/strong&gt;, não só agregado. Uma partição quente e parada desaparece numa média.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;enable.idempotence=true&lt;/code&gt;&lt;strong&gt; no produtor&lt;/strong&gt;, confirmado no cliente e não presumido.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Esquema versionado e registrado&lt;/strong&gt;, com compatibilidade verificada na publicação.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plano de migração para KRaft&lt;/strong&gt; se você ainda está em 3.x — com data, não com intenção.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Alguém no time que saiba responder, sem olhar o código, se o consumidor commita antes ou depois.&lt;/strong&gt; Se ninguém souber, essa é a primeira tarefa.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;O que levar para a segunda-feira&lt;/h2&gt;&lt;p&gt;O incidente da abertura não foi causado pelo ZooKeeper, e não teria sido evitado pelo KRaft. Nenhum painel teria pego, porque não havia nada quebrado para pegar. Foi causado por uma suposição: a de que o broker entrega uma vez só.&lt;/p&gt;&lt;p&gt;A anatomia do Kafka mudou de verdade nos últimos dois anos — o coordenador externo saiu, o quórum interno entrou, as filas nativas chegaram, o rebalanceamento parou de parar o mundo. Vale atualizar o desenho na sua cabeça, e vale mais ainda se você aprendeu tudo isso antes de 2025.&lt;/p&gt;&lt;p&gt;Mas a mudança que importa não é essa. É perceber que coordenação não é durabilidade, que durabilidade é configuração explícita, e que entrega única não é algo que você recebe do broker — é algo que você constrói no seu lado da linha, com chave de idempotência gravada junto com o efeito.&lt;/p&gt;&lt;p&gt;O Kafka é honesto sobre o que entrega. O problema é que a gente conta a história dele de um jeito que parece prometer mais.&lt;/p&gt;&lt;p&gt;E você, qual suposição sobre o seu broker só descobriu que era falsa durante um incidente? Conta nos comentários — eu começo com a minha, que está escrita lá em cima, num artigo de dois anos atrás.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;Notas&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Broker&lt;/strong&gt; — cada servidor do cluster Kafka. É a agência dos correios; o cluster é a rede de agências.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Cluster&lt;/strong&gt; — o conjunto de brokers que funciona como um sistema só.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Produtor&lt;/strong&gt; — a aplicação que publica mensagens. O remetente.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Consumidor&lt;/strong&gt; — a aplicação que lê mensagens. O destinatário.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Consumer group&lt;/strong&gt; — time de consumidores que divide as partições de um tópico entre si. A equipe de carteiros de uma agência.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Rebalance&lt;/strong&gt; — a redistribuição das partições quando alguém entra ou sai do grupo. A troca de ruas entre os carteiros.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Partição&lt;/strong&gt; — a subdivisão de um tópico, e a única unidade em que existe garantia de ordem.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Offset&lt;/strong&gt; — o número de ordem de uma mensagem dentro de uma partição. É o número da página, não a página.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Commit de offset&lt;/strong&gt; — o ato do consumidor de registrar até onde já processou. O que não chegou a acontecer no caso do Rafael.&lt;/p&gt;&lt;p&gt;&lt;code&gt;__consumer_offsets&lt;/code&gt; — o tópico interno onde o Kafka guarda os offsets commitados de cada grupo.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;At-least-once&lt;/strong&gt; — garantia de que a mensagem chega pelo menos uma vez, podendo chegar mais de uma.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;At-most-once&lt;/strong&gt; — garantia de que a mensagem chega no máximo uma vez, podendo não chegar.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Exactly-once&lt;/strong&gt; — entrega única, garantida pelo Kafka apenas dentro do próprio Kafka, e não através de um banco de dados externo.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Idempotência&lt;/strong&gt; — propriedade de uma operação que produz o mesmo resultado sendo executada uma ou várias vezes.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;ISR (in-sync replicas)&lt;/strong&gt; — as réplicas de uma partição que estão em dia com o líder. São as firmas reconhecidas do cheque.&lt;/p&gt;&lt;p&gt;&lt;code&gt;acks&lt;/code&gt; — quantas confirmações o produtor espera antes de considerar a escrita bem-sucedida.&lt;/p&gt;&lt;p&gt;&lt;code&gt;min.insync.replicas&lt;/code&gt; — o número mínimo de réplicas em dia para que uma escrita com &lt;code&gt;acks=all&lt;/code&gt; seja aceita.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Unclean leader election&lt;/strong&gt; — permitir que uma réplica atrasada vire líder. Volta mais rápido, perde dado.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Retenção&lt;/strong&gt; — o prazo ou tamanho após o qual o Kafka apaga a mensagem, tenha ela sido lida ou não.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Compactação de log&lt;/strong&gt; — regime de retenção que mantém o último valor de cada chave indefinidamente.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;KRaft&lt;/strong&gt; — o modo de consenso interno do Kafka, baseado no algoritmo Raft, que substituiu o ZooKeeper na linha 4.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Controller&lt;/strong&gt; — o broker que participa do quórum de decisões de metadados no modo KRaft. O morador eleito para a assembleia.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Quórum&lt;/strong&gt; — a maioria necessária para uma decisão valer. Sem ela, o cluster congela em vez de arriscar duas verdades.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Share group&lt;/strong&gt; — grupo de consumo cooperativo introduzido pela KIP-932, com reconhecimento por registro e sem amarração de partição.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Registro de esquemas&lt;/strong&gt; — serviço que versiona e valida o contrato das mensagens antes da publicação.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Lag&lt;/strong&gt; — a distância entre o fim da partição e o ponto onde o grupo está. É o atraso do consumidor.&lt;/p&gt;&lt;div&gt;&lt;hr&gt;&lt;/div&gt;&lt;h2&gt;Fontes&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://kafka.apache.org/blog/2025/03/18/apache-kafka-4.0.0-release-announcement/&quot;&gt;Apache Kafka 4.0.0 Release Announcement&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://kafka.apache.org/blog/2025/09/04/apache-kafka-4.1.0-release-announcement/&quot;&gt;Apache Kafka 4.1.0 Release Announcement&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://kafka.apache.org/blog/2026/02/17/apache-kafka-4.2.0-release-announcement/&quot;&gt;Apache Kafka 4.2.0 Release Announcement&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://kafka.apache.org/blog/2026/05/22/apache-kafka-4.3.0-release-announcement/&quot;&gt;Apache Kafka 4.3.0 Release Announcement&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://cwiki.apache.org/confluence/x/HhD1D&quot;&gt;KIP-848: The Next Generation of the Consumer Rebalance Protocol&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://cwiki.apache.org/confluence/x/4hA0Dw&quot;&gt;KIP-932: Queues for Kafka&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://cwiki.apache.org/confluence/spaces/KAFKA/pages/165221843/KIP-679+Producer+will+enable+the+strongest+delivery+guarantee+by+default&quot;&gt;KIP-679: Producer will enable the strongest delivery guarantee by default&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://cwiki.apache.org/confluence/spaces/KAFKA/pages/191336857/KIP-794+Strictly+Uniform+Sticky+Partitioner&quot;&gt;KIP-794: Strictly Uniform Sticky Partitioner&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://kafka.apache.org/documentation/&quot;&gt;Documentação oficial do Apache Kafka&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://chroniclesofapragmaticprogrammer.substack.com/p/anatomia-do-kafka&quot;&gt;Anatomia do Kafka — artigo anterior desta série&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Dados técnicos verificados em 11 de outubro de 2026.&lt;/p&gt;</summary><author><name>Rafael Miguel</name></author><source gr:stream-id="feed/https://chroniclesofapragmaticprogrammer.substack.com/feed"><id>tag:google.com,2005:reader/feed/https://chroniclesofapragmaticprogrammer.substack.com/feed</id><title type="html">Chronicles of a Pragmatic Programmer</title><link rel="alternate" href="https://chroniclesofapragmaticprogrammer.substack.com" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792475460000"><id gr:original-id="https://karlosmid.com/2026/10/my-learning-bits-for-tuesday-20th-october-2026">tag:google.com,2005:reader/item/00000563000001e1</id><category term="learning"></category><category term="leadership"></category><category term="ai"></category><category term="testing"></category><category term="health"></category><title type="html">My learning bits for Tuesday of 20th October 2026</title><published>2026-10-20T05:51:00Z</published><updated>2026-10-20T05:51:00Z</updated><link rel="alternate" href="https://karlosmid.com/2026/10/my-learning-bits-for-tuesday-20th-october-2026/" type="text/html"></link><summary type="html">&lt;p&gt;Here are my learning bits for today:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Alan Page uses a lesson from &lt;em&gt;Hadestown&lt;/em&gt; to explain &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://angryweasel.substack.com/p/hadestown-promises&quot; rel=&quot;noopener noreferrer&quot;&gt;why leaders should distinguish what they know, what they do not know, and what they merely hope for&lt;/a&gt;. When someone asks a concrete question, an honest “I don’t know yet” is better than vague reassurance. Leaders should make smaller, specific promises that are within their control—and say when they expect to know more.&lt;/li&gt;
  &lt;li&gt;Seth Godin suggests &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://seths.blog/2026/10/two-commuting-hacks/&quot; rel=&quot;noopener noreferrer&quot;&gt;two better uses for commuting time&lt;/a&gt;. An organization can create a short internal podcast to keep its people aligned, while an individual can replace mindless scrolling with an LLM acting as a Socratic tutor. When driving, voice mode can turn the commute into an ongoing conversation about something difficult we want to learn.&lt;/li&gt;
  &lt;li&gt;Maaret Pyhäjärvi argues that &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://visible-quality.blogspot.com/2026/10/ideation-and-oracles-where-ai-does.html&quot; rel=&quot;noopener noreferrer&quot;&gt;AI is already better than many testers at two valuable parts of testing&lt;/a&gt;: identifying domain-specific properties that should always hold, and generating more test ideas than people typically can. Our advantage is in asking critical questions, classifying those ideas for the current context, and deciding which risks matter most.&lt;/li&gt;
  &lt;li&gt;Early results presented by Novo Nordisk and Eli Lilly suggest that &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://trial.medpath.com/news/novo-nordisk-and-eli-lilly-report-glp-1-signals-of-slowed-biological-aging-at-ardd&quot; rel=&quot;noopener noreferrer&quot;&gt;the GLP-1 drugs semaglutide and tirzepatide may slow some molecular measures of biological aging&lt;/a&gt;. This does not yet prove that they slow aging itself: some of the data came from a small study without a control group, the benefit for healthy people is unknown, and the drugs can have side effects, including muscle loss.&lt;/li&gt;&lt;/ul&gt;</summary><author><name></name></author><source gr:stream-id="feed/https://blog.tentamen.eu/feed/"><id>tag:google.com,2005:reader/feed/https://blog.tentamen.eu/feed/</id><title type="html">Tentamen Software Testing Blog</title><link rel="alternate" href="https://karlosmid.com/" type="text/html"></link></source></entry><entry gr:crawl-timestamp-msec="1792467119000"><id gr:original-id="https://medium.com/p/4b8a611d6c7c">tag:google.com,2005:reader/item/0000085d0000001a</id><title type="html">I’m moving</title><published>2026-10-20T03:31:59Z</published><updated>2026-10-20T03:31:59Z</updated><link rel="alternate" href="https://medium.com/@john.gluck/im-moving-4b8a611d6c7c?source=rss-e071c348813f------2" type="text/html"></link><summary type="html">&lt;p&gt;Medium is a self-inflicted paywall&lt;/p&gt;&lt;figure&gt;&lt;img class=&quot;bqrUnknownImgSize&quot; alt src=&quot;https://cdn-images-1.medium.com/max/1024/0*a7cdRb9TE2BT3c0l&quot;&gt;&lt;figcaption&gt;Photo by &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://unsplash.com/@alexandermils&quot;&gt;Alexander Mils&lt;/a&gt; on &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://unsplash.com&quot;&gt;Unsplash&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Okay, I’m a little tipsy. Maybe I’ll bring this down in the morning, but, honestly, it has been a while in coming.&lt;/p&gt;&lt;p&gt;I get laid off alot. Occupational hazard. Medium basically got me my most recent job, from which I have recently been laid off. After the layoff before that, I had a few blog entries I wanted to write and I switched over here. Turns out, they were better at SEO than Blogspot. I think that’s about all they have going for them.&lt;/p&gt;&lt;p&gt;I wrote &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://medium.com/@john.gluck/the-cost-of-automated-testing-69e0730d891e&quot;&gt;this&lt;/a&gt; article. The director of of field marketing (who, honestly, had no idea what-the-fuck) at QA Wolf (which I backlink to in the article) was checking backlinks ffs, and he sent that article to their head of Content and Comms, who reached out to me via LinkedIn to ask me if I wanted to freelance. I was underemployed, so I my response was “Absolutely.” Best job and boss I ever had. For almost three years.&lt;/p&gt;&lt;p&gt;In the interim, Medium has not really done much for me. It has, unbeknownst to me until recently, acquired a reputation for being a place where where people feel like they can charge for their mid content.&lt;/p&gt;&lt;p&gt;So, I’m done.&lt;/p&gt;&lt;p&gt;I have a new thing, over at &lt;a target=&quot;_blank&quot; rel=&quot;noopener&quot; href=&quot;https://aicheapskate.substack.com&quot;&gt;Substack&lt;/a&gt;. I’ll leave things up here, though the best ones are already duplicated over at Substack. And I won’t ask you for money or paywall my content because I was raised on the open internet (back went “open” actually meant something) where we all shared our ideas and were all better for it. You also won’t get any AI slop from me there.&lt;/p&gt;&lt;p&gt;Okay, I admit it. I went through a phase. I’m over it.&lt;/p&gt;&lt;p&gt;Anyways, come hang with me.&lt;/p&gt;</summary><author><name>John Gluck</name></author><source gr:stream-id="feed/https://medium.com/feed/@john.gluck"><id>tag:google.com,2005:reader/feed/https://medium.com/feed/@john.gluck</id><title type="html">Stories by John Gluck on Medium</title><link rel="alternate" href="https://medium.com/@john.gluck?source=rss-e071c348813f------2" type="text/html"></link></source></entry></feed>