<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Stefano Amorelli]]></title><description><![CDATA[Stefano Amorelli]]></description><link>https://blog.amorelli.tech</link><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 11:35:40 GMT</lastBuildDate><atom:link href="https://blog.amorelli.tech/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Open-source financial research with LLMs, MCP, and the US SEC EDGAR]]></title><description><![CDATA[❗
EDGAR® and SEC® are trademarks of the U.S. Securities and Exchange Commission. This blog post and the related open-source project are not affiliated with, endorsed by, or connected in any way to the U.S. Securities and Exchange Commission.


Overvi...]]></description><link>https://blog.amorelli.tech/open-source-financial-research-with-llms-mcp-and-the-us-sec-edgar</link><guid isPermaLink="true">https://blog.amorelli.tech/open-source-financial-research-with-llms-mcp-and-the-us-sec-edgar</guid><category><![CDATA[finance]]></category><category><![CDATA[AI]]></category><category><![CDATA[llm]]></category><category><![CDATA[mcp]]></category><dc:creator><![CDATA[Stefano Amorelli]]></dc:creator><pubDate>Mon, 21 Jul 2025 11:50:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1753111534709/8679d1eb-de30-4b5c-9a9e-45b248431187.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div data-node-type="callout">
<div data-node-type="callout-emoji">❗</div>
<div data-node-type="callout-text">EDGAR® and SEC® are trademarks of the U.S. Securities and Exchange Commission. This blog post and the related open-source project are not affiliated with, endorsed by, or connected in any way to the U.S. Securities and Exchange Commission.</div>
</div>

<h2 id="heading-overview">Overview</h2>
<p>In 1934, US Congress created the Securities and Exchange Commission (SEC) to oversee financial markets and protect investors.</p>
<p>The agency was built on a simple principle: investors deserve accurate, truthful, and complete information about the companies they want to invest in.</p>
<blockquote>
<p><img src="https://i.ebayimg.com/images/g/8zoAAOSwf8thB9KN/s-l400.jpg" alt="Franklin Roosevelt 4&quot; x 6&quot; Re-Print Photo w/ FREE acid free clear Top  Loader #3 | eBay" class="image--center mx-auto" /></p>
<p>[…] “those who seek to draw upon other people's money must be wholly candid regarding the facts on which the investor's judgment is asked.” […]</p>
<p>- Franklin Delano Roosevelt, 32nd President of the United States</p>
</blockquote>
<p>For nearly a century, the SEC has served as the financial world's transparency watchdog, requiring public companies to disclose everything from quarterly earnings to executive compensation, from business risks to major corporate events.</p>
<p>Through its <a target="_blank" href="https://www.sec.gov/edgar/search/">Electronic Data Gathering, Analysis, and Retrieval (<code>EDGAR</code>)</a> system (launched in the 1990s) the SEC has made these filings freely available to anyone with an internet connection.</p>
<p>In theory, this created a level playing field where individual investors could access the same data as professional investors. In practice, however, there is a gap.</p>
<p>Institutional investors have analysts, sophisticated software, and millions in technology infrastructure to parse, analyze, and extract insights from SEC filings. <strong>Retail investors don’t</strong>. They need to manually navigate through dense, complex documents that often span hundreds of pages.</p>
<p><strong>For example, a single Apple annual report (</strong><code>10-K</code> <strong>filing) contains over 100 pages of financial data, business descriptions, and risk factors.</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752946110794/8465b189-38fa-442a-b08f-67e46b574615.png" alt="Page 1 of 121 of the 2024 Apple’s annual report (10-K)" class="image--center mx-auto" /></p>
<p><em>Page 1 of 121 of the 2024 Apple’s annual report (</em><code>10-K</code><em>)</em></p>
<hr />
<p><strong>The problem goes beyond just having access to data.</strong> Professional investors systematically extract specific metrics, compare trends across quarters and years, analyze segment performance, and identify patterns that would be nearly impossible to spot through manual review.</p>
<p><strong>But today things have changed, individual investors can now keep up.</strong></p>
<p>Large language models, the Model Context Protocol, and programmatic access to SEC EDGAR data are finally making sophisticated financial research accessible to individual investors. You can now use AI to extract key financial metrics, perform complex analyses across multiple companies and time periods, and uncover insights that previously required specialized expertise and expensive tools.</p>
<p>How exactly does this work in practice? Let's dive into the technical foundation that makes this possible: the <a target="_blank" href="https://github.com/stefanoamorelli/sec-edgar-mcp">SEC EDGAR MCP Server</a> (released with version <code>1-alpha</code> on the 21th July 2025), an open-source software built in public and maintained by the community that transforms the way how investors interact with financial research.</p>
<h2 id="heading-how-ai-changes-everything">How AI changes everything</h2>
<p>Before we dive into the technical details, let's understand what makes this approach fundamentally different from traditional financial research tools and workflows.</p>
<p>The Model Context Protocol (MCP) is an open standard that allows AI assistants to securely connect to external data sources and tools. Think of it as a universal connector that lets your AI assistant access and speak directly to databases, APIs, and services, including the SEC's EDGAR system.</p>
<p>Traditional financial research often involves jumping between multiple platforms: searching for companies on one site, downloading filings from another, then manually copying data into spreadsheets for analysis.</p>
<p><strong>With an MCP server, your AI assistant can do all of this seamlessly in a single conversation.</strong></p>
<h3 id="heading-from-raw-data-to-intelligence">From raw data to intelligence</h3>
<p>Let's look at a practical example. Suppose you want to analyze Microsoft's financial data. Traditionally, this would involve:</p>
<ol>
<li><p>Finding MSFT's recent 10-Q (quarterly report) or 10-K (annual report) filings on EDGAR</p>
</li>
<li><p>Downloading and opening multiple PDF documents</p>
</li>
<li><p>Manually searching for segment revenue data</p>
</li>
<li><p>Copying numbers into a spreadsheet</p>
</li>
<li><p>Calculating growth rates and trends</p>
</li>
</ol>
<p>With the SEC EDGAR MCP, this entire process becomes a simple conversation: <em>"Show me Microsoft balance sheet, income statement, and cashflow data."</em> The AI assistant handles all the technical complexity behind the scenes and presents you with clean, formatted results:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/HEWTR15-xZc">https://youtu.be/HEWTR15-xZc</a></div>
<p> </p>
<p>Another example would be to analyze Apple’s latest revenue:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/T-6H3zF8fWU">https://youtu.be/T-6H3zF8fWU</a></div>
<p> </p>
<p>Or create a dashboard with charts, on the fly, based on the latest NVIDIA financial data:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/UH2JPfhuoGU">https://youtu.be/UH2JPfhuoGU</a></div>
<p> </p>
<p>We could also search for the latest insider transactions in Amazon:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/k4sVFcHpmfg">https://youtu.be/k4sVFcHpmfg</a></div>
<p> </p>
<p>Or investigate company-specific data entries from the Apple filings, and plot them:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/V2rSkgYZXGg">https://youtu.be/V2rSkgYZXGg</a></div>
<p> </p>
<p>To use this workflow, you'll need a running LLM that supports the MCP protocol, such as <a target="_blank" href="https://claude.ai/download">Claude Desktop</a> (used in the demos). The server runs locally via Docker.</p>
<p>You can follow the instructions on how to install and use it <a target="_blank" href="https://sec-edgar-mcp.amorelli.tech/setup/quickstart">here</a>.</p>
<hr />
<p>But wait, the SEC does provide APIs for accessing EDGAR data. So why not just use those directly?</p>
<h3 id="heading-why-not-egar-api">Why not EGAR API?</h3>
<p>The answer lies in complexity and usability. The SEC's REST APIs are powerful but require technical expertise to use effectively. You need to understand company identifiers (CIKs), filing taxonomies, XBRL structures, and how to navigate complex JSON responses. Also, you’d need to know how to code.</p>
<p>For a simple question like <em>"What was Apple's revenue last quarter?"</em> you'd need to write a software to find Apple's <code>CIK</code> (Central Identifier Key), locate the right filing, parse <code>XBRL</code> data, and extract the specific financial concept. All of this before you even get to analysis.</p>
<p>This complexity naturally leads to another question: why not just ask ChatGPT or other AI assistants directly about financial data, without an MCP server?</p>
<h3 id="heading-why-not-general-llms">Why not general LLMs?</h3>
<p>The challenge here is accuracy and currency. General-purpose AI models are trained on data with cutoff dates, meaning they lack recent financial information. While they can navigate the web and try to find the financial data, they might miss important details. When you're making investment decisions, you need current, verified, and complete data from the source.</p>
<p>That's exactly the problem the SEC EDGAR MCP server was designed to solve.</p>
<p>As you can see from this <a target="_blank" href="https://claude.ai/share/77170acd-fa0a-4763-9403-328734a3fc8f">conversation</a>, the LLM connected to the MCP server is able to consume the information based on the original filing, the best source of information available:</p>
<blockquote>
<p>All data is sourced directly from NVIDIA's SEC EDGAR filing (Form 10-Q, filed May 28, 2025, Accession Number: 0001045810-25-000116) with exact precision preserved from the original XBRL data.</p>
</blockquote>
<h2 id="heading-inner-workings-of-the-mcp-server">Inner workings of the MCP server</h2>
<p>This open-source package is freely available for anyone to use, modify, and improve. The package provides over 20 specialized tools to LLMs, that handle everything from finding company filings to extracting complex financial metrics:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753095400777/27ad2569-52ca-4c56-90c2-13ed658df562.png" alt class="image--center mx-auto" /></p>
<p>You can read more in details about each tool in the <a target="_blank" href="https://sec-edgar-mcp.amorelli.tech/tools/overview#company-tools">documentation</a>.</p>
<p>Here's what makes it powerful:</p>
<p><strong>Smart data extraction and parsing</strong>: Instead of manually parsing through hundreds of pages of financial documents, the package can automatically extract specific metrics like revenue by geographic segment, quarterly comparisons, or executive compensation data.</p>
<p><strong>Multiple data sources</strong>: The package taps into several SEC data streams, from the main EDGAR database to real-time RSS feeds of new filings, so that you have access to both historical data and the latest company updates.</p>
<p><strong>XBRL analysis</strong>: Modern SEC filings use XBRL (eXtensible Business Reporting Language), a structured format that makes financial data machine-readable. The package understands XBRL natively, allowing it to extract precise financial concepts rather than consuming the whole document.</p>
<p><strong>Company-specific insights</strong>: Different companies report data differently. Apple might break down revenue by "Americas, Europe, and Greater China" while Microsoft uses different regional categories. The package dynamically discovers and adapts to each company's specific reporting structure.</p>
<h3 id="heading-open-source-why-it-matters">Open-source, why it matters?</h3>
<p><strong>The decision to make this package open source isn't just about free access, it's about transparency and community-driven innovation.</strong></p>
<p>Financial tools shouldn't be black boxes. When you're making investment decisions, you need to trust not just the data, but the methods used to extract and analyze it.</p>
<p>Open source means you can inspect exactly how the package works, contribute improvements, and adapt it for your specific needs. It also means the tool can evolve with the community, incorporating new features.</p>
<h1 id="heading-looking-forward">Looking forward</h1>
<p>The SEC has been collecting corporate disclosures for nearly 90 years. The data is all there, freely available to anyone. But until now, extracting meaningful insights from that data required time, deep technical expertise, and expensive analytical tools.</p>
<p>With MCP and LLMs individual investors can ask questions in plain English and get precise answers backed by official SEC filings.</p>
<p>It’s not a revolutionary technology, it's simply good engineering applied to a real problem. The SEC EDGAR already provides APIs, companies already file in structured formats, AI assistants already exist.</p>
<p><strong>The MCP just connects these pieces together in a way that's actually useful for investors.</strong></p>
<p>Roosevelt wanted markets where individual investors could make informed decisions. The SEC provided the transparency. <strong>Now open-source tools are providing the accessibility</strong>. What took teams of analysts before can now be done in a conversation by anyone.</p>
<p><strong>Maybe the information advantage that Wall Street has held for decades is disappearing.</strong></p>
<hr />
<h1 id="heading-acknowledgements">Acknowledgements</h1>
<p>This work wouldn't be possible without the foundation laid by many others.</p>
<p><strong>The US SEC</strong> deserves recognition for the incredible work of maintaining one of the world's most comprehensive and accessible corporate disclosure systems. The EDGAR database and REST APIs provide the reliable data foundation that makes tools like this possible.</p>
<p><strong>Anthropic</strong> created the Model Context Protocol standard and continues to advance the field of AI safety and capability. Their commitment to open standards enables the kind of interoperability that benefits everyone.</p>
<h2 id="heading-links">Links</h2>
<h3 id="heading-sec-edgar-mcp-package">SEC EDGAR MCP package</h3>
<ul>
<li><p><strong>GitHub Repository</strong>: <a target="_blank" href="https://github.com/stefanoamorelli/sec-edgar-mcp">https://github.com/stefanoamorelli/sec-edgar-mcp</a></p>
</li>
<li><p><strong>Documentation</strong>: <a target="_blank" href="https://sec-edgar-mcp.amorelli.tech">https://sec-edgar-mcp.amorelli.tech</a></p>
</li>
</ul>
<h3 id="heading-sec-resources">SEC resources</h3>
<ul>
<li><p><strong>EDGAR Database</strong>: <a target="_blank" href="https://www.sec.gov/edgar">https://www.sec.gov/edgar</a></p>
</li>
<li><p><strong>SEC REST APIs</strong>: <a target="_blank" href="https://www.sec.gov/edgar/sec-api-documentation">https://www.sec.gov/edgar/sec-api-documentation</a></p>
</li>
<li><p><strong>EDGAR Company Search</strong>: <a target="_blank" href="https://www.sec.gov/edgar/searchedgar/companysearch">https://www.sec.gov/edgar/searchedgar/companysearch</a></p>
</li>
<li><p><strong>SEC Investor.gov</strong>: <a target="_blank" href="https://www.investor.gov/">https://www.investor.gov/</a></p>
</li>
</ul>
<h3 id="heading-model-context-protocol-mcp">Model context protocol (MCP)</h3>
<ul>
<li><p><strong>MCP Specification</strong>: <a target="_blank" href="https://modelcontextprotocol.io/">https://modelcontextprotocol.io/</a></p>
</li>
<li><p><strong>Anthropic MCP Documentation</strong>: <a target="_blank" href="https://docs.anthropic.com/en/docs/build-with-claude/computer-use">https://docs.anthropic.com/en/docs/build-with-claude/computer-use</a></p>
</li>
<li><p><strong>MCP Servers Repository</strong>: <a target="_blank" href="https://github.com/modelcontextprotocol/servers">https://github.com/modelcontextprotocol/servers</a></p>
</li>
</ul>
<h3 id="heading-open-source-packages">Open-source packages</h3>
<ul>
<li><p><code>edgartools</code> (by <a target="_blank" href="https://www.linkedin.com/in/dwight-gunning/">Dwight Gunning</a>): <a target="_blank" href="https://github.com/dgunning/edgartools">https://github.com/dgunning/edgartools</a></p>
</li>
<li><p><code>datamule</code> (by <a target="_blank" href="https://www.linkedin.com/in/johngfriedman/">John Friedman</a>): <a target="_blank" href="https://github.com/john-friedman/datamule-python">https://github.com/john-friedman/datamule-python</a></p>
</li>
</ul>
<h3 id="heading-financial-data-and-analysis">Financial data and analysis</h3>
<ul>
<li><p><strong>XBRL International</strong>: <a target="_blank" href="https://www.xbrl.org/">https://www.xbrl.org/</a></p>
</li>
<li><p><strong>SEC XBRL Information</strong>: <a target="_blank" href="https://www.sec.gov/structureddata/osd-inline-xbrl.html">https://www.sec.gov/structureddata/osd-inline-xbrl.html</a></p>
</li>
<li><p><strong>OpenFIGI (Financial Instrument Global Identifier)</strong>: <a target="_blank" href="https://www.openfigi.com/">https://www.openfigi.com/</a></p>
</li>
<li><p><strong>Financial Data Transparency Act</strong>: <a target="_blank" href="https://www.congress.gov/bill/117th-congress/house-bill/2989">https://www.congress.gov/bill/117th-congress/house-bill/2989</a></p>
</li>
</ul>
<h3 id="heading-historical-context">Historical context</h3>
<ul>
<li><p><strong>Securities Act of 1933</strong>: <a target="_blank" href="https://www.investor.gov/introduction-investing/investing-basics/role-sec/laws-govern-securities-industry#secact1933">https://www.investor.gov/introduction-investing/investing-basics/role-sec/laws-govern-securities-industry#secact1933</a></p>
</li>
<li><p><strong>Securities Exchange Act of 1934</strong>: <a target="_blank" href="https://www.investor.gov/introduction-investing/investing-basics/role-sec/laws-govern-securities-industry#secexact1934">https://www.investor.gov/introduction-investing/investing-basics/role-sec/laws-govern-securities-industry#secexact1934</a></p>
</li>
<li><p><strong>Franklin D. Roosevelt Presidential Library</strong>: <a target="_blank" href="https://www.fdrlibrary.org/">https://www.fdrlibrary.org/</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Efficient large context management in AWS Strands Agents]]></title><description><![CDATA[Imagine: your AI agent has been working on a complex analysis for hours. It's pulled data from multiple sources, run various calculations, identified patterns, and made several key discoveries.
Then it hits a wall. It found something interesting but ...]]></description><link>https://blog.amorelli.tech/efficient-large-context-management-in-aws-strands-agents</link><guid isPermaLink="true">https://blog.amorelli.tech/efficient-large-context-management-in-aws-strands-agents</guid><category><![CDATA[AI]]></category><category><![CDATA[AWS]]></category><category><![CDATA[genai]]></category><category><![CDATA[agentic AI]]></category><category><![CDATA[agents]]></category><dc:creator><![CDATA[Stefano Amorelli]]></dc:creator><pubDate>Wed, 25 Jun 2025 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751227412720/83c3d37f-2afd-44bc-8431-f1d77e8e4a9b.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Imagine: your AI agent has been working on a complex analysis for hours. It's pulled data from multiple sources, run various calculations, identified patterns, and made several key discoveries.</p>
<p>Then it hits a wall. It found something interesting but it can't remember the earlier context.</p>
<p>Which datasets were involved? What were the baseline findings?</p>
<p><strong>The context window filled up. Your agent essentially forgot hours of work!</strong></p>
<p>Extended AI agents sessions can hit this wall easily, and analytical workflows suffer the most because continuity matters.</p>
<p><code>SummarizingConversationManager</code> (released in <code>Strands Agents v0.1.8</code>) addresses this challenge by implementing context compression. Rather than simply truncating old messages when the context window fills up, it creates a summary that preserve the essential information from the previous messages:</p>
<p><img src="https://assets.community.aws/a/2z3ked3LaSl78pM7I51KL7QSLgC/Scre.webp?imgSize=570x1000" alt /></p>
<p>This approach maintains a coherent conversational flow while reducing context size. In a way, it operates like a real analyst: <strong>it remembers the most important bits and discards the details to make space for new data</strong>.</p>
<p>Let's see how you can use it!</p>
<h2 id="heading-configuration-options-for-summarizing-context-management"><strong>Configuration options for summarizing context management</strong></h2>
<p><strong>A key principle of</strong> <code>Strands Agents</code> <strong>is that it lets you build powerful workflows with minimal code, and the</strong> <code>SummarizingConversationManager</code> <strong>follows the same philosophy.</strong> It comes configured with sensible defaults and works immediately for most use cases:</p>
<p>With this configuration, when the agent hits the context limit it automatically summarizes previous conversations to extend the available context space.</p>
<p>But let's explore how you can have more control over the summarization process.</p>
<h3 id="heading-delegating-summarization-to-specialized-agents"><strong>Delegating summarization to specialized agents</strong></h3>
<p>One approach is to use a separate agent specifically for creating summaries. This allows you to optimize the summarization independently from your main agent's configuration. For instance, you might want to use a model that's particularly good at distilling complex technical information:</p>
<p>This separation also means you can give the summarizer agent specific instructions or prompts that differ from your main agent's role. The summarizer might focus on extracting methodological details and quantitative results, while your main agent maintains its broader analytical perspective.</p>
<h3 id="heading-controlling-summary-granularity"><strong>Controlling summary granularity</strong></h3>
<p>With <code>summary_ratio</code> we can determine how much compression occurs during summarization. This value represents the target ratio between the summary length and the original content length.</p>
<p>Higher <code>summary_ratio</code> values produce more aggressive compression, resulting in brief, high-level summaries that capture only the most essential points. Lower values create more detailed summaries that preserve additional context and nuance:</p>
<p><strong>The optimal ratio depends on your use case.</strong> Exploratory data analysis might benefit from detailed summaries that preserve methodological nuances, while routine reporting workflows might work well with more compressed summaries.</p>
<h3 id="heading-preserving-recent-context"><strong>Preserving recent context</strong></h3>
<p>You wouldn't want your agent to immediately summarize the conversation you just had. Recent exchanges contain the freshest context and often drive the current direction of your analysis. The <code>preserve_recent_messages</code> parameter controls how many of the most recent messages remain untouched by summarization.</p>
<p>When you set this parameter, those recent messages stay in their original form, maintaining the natural conversational flow and ensuring that immediate context remains accessible:</p>
<p>This parameter requires some consideration of the agent's typical patterns. If it tends to have many short exchanges, you might want to preserve more messages. For workflows with longer, more substantial exchanges, fewer preserved messages should be sufficient.</p>
<p><strong>Custom summarization instructions</strong></p>
<p>You can also provide specific instructions for how summaries should be created through the <code>summary_prompt</code> parameter. This allows you to tailor the summarization process to your particular domain or workflow:</p>
<p><strong>Complete configuration example</strong></p>
<p>Here's how these parameters work together in a realistic scenario. This configuration might be appropriate, for example, for a data science workflow where you need to maintain methodological details while managing long analytical sessions:</p>
<p>This setup creates a system where the agent can maintain awareness of analytical workflows over extended periods. The summarizer agent focuses specifically on creating accurate summaries, while the main agent can continue its work without losing important context from earlier in the conversation.</p>
<h2 id="heading-a-note-on-large-context-foundation-models">A note on large-context foundation models</h2>
<p>If new models support huge context windows, why bother with summarization? Sure, you could throw millions of tokens at the problem and let your agent handle an endless conversation history (like Claude 3.5 Sonnet with 200K tokens, Gemini 1.5 Pro with 1M tokens, or GPT-4 Turbo with 128K tokens).</p>
<p>But most of that context can easily become irrelevant noise, and agents slow down if they need to process massive amounts of data on every request. <strong>Costs also spike because you're paying for a lot of tokens that might not add much value</strong>. And paradoxically, performance often degrades. In a ocean of context agents struggle to identify what actually matters.</p>
<p>Think about how you work on complex projects. You don't reread every email, every draft, every brainstorming session. You keep the key insights, the important decisions. You compress intelligently. That's exactly what <code>SummarizingConversationManager</code> does.</p>
<p>Maybe agents don't need to remember everything, just the right things!</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p><a target="_blank" href="https://aws.amazon.com/blogs/opensource/introducing-strands-agents-an-open-source-ai-agents-sdk/">Strands Agents</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/strands-agents/sdk-python/releases/tag/v0.1.8">Strands Agents v0.1.8 Release</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/strands-agents/sdk-python/pull/112">SummarizingConversationManager Implementation</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/strands-agents/docs/pull/63">SummarizingConversationManager Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://arxiv.org/abs/2308.15022">Recursively Summarizing Enables Long-Term Dialogue Memory in Large Language Models</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How AI agents pay onchain.]]></title><description><![CDATA[It's January 1997.
The IETF (Internet Engineering Task Force) has just released RFC 2068, officially defining HTTP/1.1. The specification was authored by web pioneers Roy Fielding, Jim Gettys, Jeffrey Mogul, Henrik Frystyk, and Tim Berners-Lee, the a...]]></description><link>https://blog.amorelli.tech/how-ai-agents-pay-onchain</link><guid isPermaLink="true">https://blog.amorelli.tech/how-ai-agents-pay-onchain</guid><category><![CDATA[genai]]></category><category><![CDATA[AI]]></category><category><![CDATA[fintech]]></category><category><![CDATA[Cryptocurrency]]></category><category><![CDATA[Artificial Intelligence]]></category><dc:creator><![CDATA[Stefano Amorelli]]></dc:creator><pubDate>Tue, 17 Jun 2025 15:47:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750162012232/ef02a9f6-846c-4543-9356-4f3a9fb861d4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It's January 1997.</p>
<p>The <a target="_blank" href="https://www.ietf.org/"><strong>IETF</strong></a> (Internet Engineering Task Force) has just released <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc2068.html"><strong>RFC 2068</strong></a>, officially defining <strong>HTTP/1.1</strong>. The specification was authored by web pioneers <a target="_blank" href="https://en.wikipedia.org/wiki/Roy_Fielding"><strong>Roy Fielding</strong></a>, <a target="_blank" href="https://en.wikipedia.org/wiki/Jim_Gettys"><strong>Jim Gettys</strong></a>, <a target="_blank" href="https://en.wikipedia.org/wiki/Jeffrey_Mogul"><strong>Jeffrey Mogul</strong></a>, <a target="_blank" href="https://en.wikipedia.org/wiki/Henrik_Frystyk_Nielsen"><strong>Henrik Frystyk</strong></a>, and <a target="_blank" href="https://en.wikipedia.org/wiki/Tim_Berners-Lee"><strong>Tim Berners-Lee</strong></a>, the architects who shaped how the internet communicates.</p>
<p>The specification introduces <strong>persistent connections</strong>: previously, every single HTTP request required a fresh TCP connection. Persistent connections resolve this, allowing multiple HTTP requests to flow through a single, long-lived TCP connection. No more establishing separate connections for every image, CSS file, or JavaScript snippet on a web page.</p>
<p>There's also <strong>chunked transfer encoding</strong>, a new way for web servers to stream content without knowing the full size beforehand. No longer does a server need to calculate the total size of dynamically generated content upfront, it's now free to deliver data incrementally, as it's produced.</p>
<p>But <strong>RFC 2068 quietly introduces something intriguing</strong>, a new status code:</p>
<pre><code class="lang-plaintext">
HTTP 402 Payment Required

   This code is reserved for future use.
</code></pre>
<p>This shows how the founding fathers of world wide web predicted how money would eventually become a big part of internet, <strong>even if they had no clear path on how it would actually play out</strong>.</p>
<p>Today, 2025, nearly three decades and multiple HTTP versions later (<code>HTTP/2</code> in 2015, <code>HTTP/3</code> in 2022), <code>Status Code 402</code> <strong>still sits there with the exact same note: 'reserved for future use.'</strong> Despite the fintech revolution, the rise of online payments, and an entire economy built on internet transactions, nobody had figured out what to do with it.</p>
<p><strong>Until now.</strong></p>
<p>Last month (May 2025), <a target="_blank" href="https://www.coinbase.com/developer-platform/discover/launches/x402?utm_source=blog.amorelli.tech">Coinbase</a> released <code>x402</code>, an open source protocol that gives <code>HTTP 402</code> its first real job: enabling native <strong>onchain</strong> payments within HTTP requests.</p>
<p>AI agents now need to make <strong>M2M</strong> (machine-to-machine) payments across the web with reduced <strong>HITL</strong> (human-in-the-loop) interventions. Traditional payment flows don’t work well in this case. They require multiple human interactions, redirects, and manual steps that simply don't work when an AI agent needs to make a transaction autonomously.</p>
<p><code>x402</code> fills this gap. It proposes an automated on-chain payment flow implemented natively within the HTTP protocol, <strong>making them as seamless as any other web request</strong>.</p>
<p>But what does this look like in practice?</p>
<h2 id="heading-architecture-and-components-of-x402">Architecture and components of <code>x402</code></h2>
<p><code>x402</code> is built around four core components:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750015703781/ceb52fad-5fa5-440c-bb92-00424b2fe323.png" alt class="image--center mx-auto" /></p>
<p>A <strong>client</strong> acts as the payment initiator, discovering what's required for access and constructing the appropriate payment payload. Put simply, this is whatever is making the HTTP request to a pay-walled resource. It could be a browser making a request for premium content, an AI agent purchasing API access, or a mobile app unlocking features. The client handles the cryptographic signing using the user's private key and automatically retries requests when payment is required.</p>
<p>The <strong>resource server</strong> enforces payment policies for its endpoints while remaining focused on its core business logic. This is the web server or API that hosts the content or service being purchased. It maintains simple pricing tables that map endpoints to costs, but delegates the payment verification logic to the facilitator.</p>
<p>Blockchain logic is implemented in the <strong>facilitator</strong> component: verifying cryptographic signatures, preventing replay attacks through nonce tracking, and managing the actual on-chain settlement. It allows both clients and servers to work with on-chain payments without understanding the blockchain implementation details.</p>
<p>On <strong>blockchain</strong> resides the final settlement layer, ensuring payments are immutable and transparent. It enables programmable money through smart contracts and stable-coins, <strong>but its complexity is completely hidden from the application layer by the facilitator</strong>.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Component</td><td>Primary Responsibility</td><td>Key Features</td><td>What it does</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Client</strong></td><td>Payment initiation</td><td><code>EIP-712</code> signing, automatic retries, payment discovery</td><td>Makes requests, handles wallets, retries with payment</td></tr>
<tr>
<td><strong>Resource Server</strong></td><td>Payment enforcement</td><td>Pricing tables, <code>HTTP 402</code> responses, middleware integration</td><td>Sets prices, checks payments, serves content</td></tr>
<tr>
<td><strong>Facilitator</strong></td><td>Payment verification</td><td>Signature verification, nonce tracking, gas abstraction</td><td>Verifies signatures, talks to blockchain</td></tr>
<tr>
<td><strong>Blockchain</strong></td><td>Payment settlement</td><td><code>USDC</code> transfers, smart contracts, immutable records</td><td>Settles payments on chain</td></tr>
</tbody>
</table>
</div><h3 id="heading-principles">Principles</h3>
<p>This architecture demonstrates several fundamental software engineering principles. The most important is <strong>separation of concerns</strong>. Each component has a single, well-defined responsibility. <strong>Resource servers focus purely on business logic, facilitators handle payment complexity, and clients manage user interaction</strong>.</p>
<p>The system achieves <strong>loose coupling</strong> by having components interact only through standardized HTTP and REST interfaces. <strong>A resource server doesn't need to understand how blockchain transactions work, and a client doesn't need to know the server's internal implementation</strong>. This isolation means you can swap out components (for example, use a different blockchain, change facilitator providers, or modify server logic) without affecting the rest of the system.</p>
<p>The facilitator embodies the <strong>single responsibility principle</strong> by isolating all blockchain complexity into one specialized service. This prevents payment logic from leaking into business applications and keeps concerns properly separated.</p>
<p>Last but not least this architecture follows <strong>dependency inversion</strong>. High-level components depend on abstractions rather than concrete implementations. Servers and clients depend on HTTP interfaces, not specific blockchain APIs. This allows the same application code to work across different blockchains and payment schemes without modification.</p>
<h3 id="heading-payment-flow">Payment flow</h3>
<p>When an AI agent or user hits an <code>x402</code>-enabled API, here's the four-step flow that happens:</p>
<ol>
<li><p><strong>Initial request</strong>: The client makes a standard HTTP request to access some resource</p>
</li>
<li><p><strong>Payment required response</strong>: If no payment is attached, the server responds with <code>HTTP 402</code> and includes payment details</p>
</li>
<li><p><strong>Payment authorization</strong>: The client creates a cryptographically signed payment and retries the request</p>
</li>
<li><p><strong>Verification and access</strong>: The server validates the payment, broadcasts it to the blockchain, and grants access</p>
</li>
</ol>
<p>What makes this powerful is that it all happens at the HTTP protocol level. No redirects to third-party payment processors, no <code>OAuth</code> flows, no account creation. <strong>Just standard HTTP with extra headers:</strong></p>
<ul>
<li><p><code>X-PAYMENT</code> flows from client to server and contains the <strong>signed payment payload</strong>. This includes the payment details (amount, recipient, token) plus a cryptographic signature proving the client authorized the payment.</p>
</li>
<li><p><code>X-PAYMENT-RESPONSE</code> flows from server to client after successful payment and contains <strong>transaction receipt information</strong>, providing transparency about what happened on-chain.</p>
</li>
</ul>
<pre><code class="lang-mermaid">sequenceDiagram
    participant C as Client&lt;br/&gt;(Apps, AI Agents, Browsers)
    participant S as Resource Server&lt;br/&gt;(APIs, Services)
    participant F as Facilitator&lt;br/&gt;(Payment Processor)
    participant B as Blockchain&lt;br/&gt;(Base, Ethereum, etc.)

    C-&gt;&gt;S: 1. Request resource
    S-&gt;&gt;C: 2. 402 Payment Required
    C-&gt;&gt;S: 3. Request + X-PAYMENT header
    S-&gt;&gt;F: 4. Verify payment
    F-&gt;&gt;B: 5. Check on-chain
    B-&gt;&gt;F: 6. Validation result
    F-&gt;&gt;S: 7. Verification response
    S-&gt;&gt;F: 8. Settle payment
    F-&gt;&gt;B: 9. Broadcast transaction
    S-&gt;&gt;C: 10. Resource + receipt
</code></pre>
<h2 id="heading-server-side-implementation">Server-side implementation</h2>
<h3 id="heading-payment-middleware-architecture">Payment middleware architecture</h3>
<p>The core server-side implementation revolves around a payment filter (AKA middle-ware) that intercepts HTTP requests and enforces payment requirements. When integrated into your web server, this middle-ware checks incoming requests against a price table that maps endpoints to their costs.</p>
<p>The middle-ware follows a simple decision tree: if a request hits a protected endpoint without payment, it responds with <code>HTTP 402</code> and detailed payment instructions. If payment is included in the <code>X-PAYMENT</code> header, it verifies the payment with a facilitator service before allowing the request to proceed.</p>
<p>Here's the essential structure from the Java implementation:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaymentFilter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Filter</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String payTo;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map&lt;String, BigInteger&gt; priceTable; <span class="hljs-comment">// path → amount</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> FacilitatorClient facilitator;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doFilter</span><span class="hljs-params">(ServletRequest request, ServletResponse response, 
                        FilterChain chain)</span> <span class="hljs-keyword">throws</span> IOException, ServletException </span>{
        String path = req.getRequestURI();
        String paymentHeader = req.getHeader(<span class="hljs-string">"X-PAYMENT"</span>);

        <span class="hljs-keyword">if</span> (!priceTable.containsKey(path)) {
            chain.doFilter(request, response);  <span class="hljs-comment">// Free endpoint</span>
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">if</span> (paymentHeader == <span class="hljs-keyword">null</span>) {
            send402Response(resp, path);  <span class="hljs-comment">// Request payment</span>
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-comment">// Verify payment, process request, then settle</span>
        VerificationResponse verification = facilitator.verify(paymentHeader, requirements);
        <span class="hljs-keyword">if</span> (verification.valid) {
            chain.doFilter(request, response);
            facilitator.settle(paymentHeader, requirements);
        }
    }
}
</code></pre>
<p><strong>The beauty of this approach is that it requires minimal changes to existing applications.</strong> You simply add the payment filter to your middle-ware stack and define which endpoints require payment.</p>
<h3 id="heading-paymentrequirements-response"><code>PaymentRequirements</code> Response</h3>
<p>When a client hits a protected endpoint without payment, the server constructs a detailed payment requirements object. This includes the payment amount, accepted tokens (like <code>USDC</code>), the receiving wallet address, blockchain network, and an expiration time to prevent replay attacks.</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">send402Response</span><span class="hljs-params">(HttpServletResponse response, String path)</span> <span class="hljs-keyword">throws</span> IOException </span>{
    response.setStatus(HttpStatus.PAYMENT_REQUIRED);
    response.setContentType(<span class="hljs-string">"application/json"</span>);

    PaymentRequirements requirements = PaymentRequirements.builder()
        .paymentRequirement(List.of(
            PaymentRequirement.builder()
                .kind(<span class="hljs-keyword">new</span> Kind(<span class="hljs-string">"exact"</span>, <span class="hljs-string">"base-sepolia"</span>))             <span class="hljs-comment">// Payment scheme + blockchain network</span>
                .receiver(payTo)                                     <span class="hljs-comment">// Wallet address to receive payment</span>
                .amount(priceTable.get(path))                        <span class="hljs-comment">// Cost for this specific endpoint</span>
                .asset(<span class="hljs-string">"&lt;USDC_TOKEN_CONTRACT&gt;"</span>)                      <span class="hljs-comment">// USDC token contract</span>
                .expiry(Instant.now().plus(Duration.ofMinutes(<span class="hljs-number">5</span>)))   <span class="hljs-comment">// Payment window</span>
                .nonce(UUID.randomUUID().toString())                 <span class="hljs-comment">// One-time use identifier</span>
                .build()
        ))
        .build();

    response.getWriter().write(Json.MAPPER.writeValueAsString(requirements));
}
</code></pre>
<p>Each field in the <code>PaymentRequirements</code> is described as follows:</p>
<ul>
<li><p><code>kind</code>: Defines the payment scheme (<code>exact</code> for fixed amounts) and target blockchain network (<code>base-sepolia</code> for Base testnet). This tells the client exactly how to structure and execute the payment.</p>
</li>
<li><p><code>receiver</code>: The wallet address where payment should be sent. This is your business wallet that will receive the funds.</p>
</li>
<li><p><code>amount</code>: The cost for accessing this specific endpoint, retrieved from your price table. For USDC, this is typically expressed in <code>wei</code> (smallest unit).</p>
</li>
<li><p><code>asset</code>: The smart contract address of the token to be used for payment. The example shows USDC on Base Sepolia testnet.</p>
</li>
<li><p><code>expiry</code>: A timestamp after which this payment requirement becomes invalid. This prevents old payment requests from being reused and adds security against replay attacks.</p>
</li>
<li><p><code>nonce</code>: A unique identifier (UUID) that ensures each payment requirement can only be fulfilled once, even if the same client makes multiple requests to the same endpoint.</p>
</li>
</ul>
<h2 id="heading-client-side-implementation">Client-side implementation</h2>
<h3 id="heading-automatic-payment-handling">Automatic payment handling</h3>
<p>Client libraries wrap standard HTTP clients to automatically handle 402 responses. When a client receives a payment requirement, (1) it constructs a payment payload, (2) signs it with the user's private key, and (3) retries the original request with the payment attached.</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> HttpResponse&lt;String&gt; <span class="hljs-title">makeRequest</span><span class="hljs-params">(String url, String method)</span> <span class="hljs-keyword">throws</span> Exception </span>{
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(url))
        .method(method, HttpRequest.BodyPublishers.noBody())
        .build();

    HttpResponse&lt;String&gt; response = httpClient.send(request, 
        HttpResponse.BodyHandlers.ofString());

    <span class="hljs-comment">// Handle 402 Payment Required</span>
    <span class="hljs-keyword">if</span> (response.statusCode() == <span class="hljs-number">402</span>) {
        PaymentRequirements requirements = Json.MAPPER.readValue(
            response.body(), PaymentRequirements.class);

        // Create payment payload matching the requirements
        PaymentPayload payment = PaymentPayload.builder()
            .receiver(requirements.getPaymentRequirement().get(<span class="hljs-number">0</span>).getReceiver())
            .amount(requirements.getPaymentRequirement().get(<span class="hljs-number">0</span>).getAmount())
            .asset(requirements.getPaymentRequirement().get(<span class="hljs-number">0</span>).getAsset())
            .nonce(requirements.getPaymentRequirement().get(<span class="hljs-number">0</span>).getNonce())
            .expiry(requirements.getPaymentRequirement().get(<span class="hljs-number">0</span>).getExpiry())
            .build();

        <span class="hljs-comment">// Sign using EIP-712 structured data signing</span>
        String signature = signer.sign(payment.toSigningMap());

        <span class="hljs-comment">// Retry with payment header</span>
        String paymentHeader = encodePaymentHeader(payment, signature);
        HttpRequest paidRequest = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .method(method, HttpRequest.BodyPublishers.noBody())
            .header(<span class="hljs-string">"X-PAYMENT"</span>, paymentHeader)
            .build();

        <span class="hljs-keyword">return</span> httpClient.send(paidRequest, HttpResponse.BodyHandlers.ofString());
    }

    <span class="hljs-keyword">return</span> response;
}
</code></pre>
<p>The signing process uses the <a target="_blank" href="https://eips.ethereum.org/EIPS/eip-712"><code>EIP-712</code></a> standard, which creates a structured, human-readable representation of the payment data before hashing and signing it. This ensures the payment is cryptographically secure and tied to the specific request.</p>
<h2 id="heading-payment-verification-flow">Payment verification flow</h2>
<h3 id="heading-facilitator-integration">Facilitator integration</h3>
<p>The facilitator service is where the blockchain complexity lives, abstracting it away from both clients and servers. When a server receives a payment, it forwards the payment payload to the facilitator for verification.</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HttpFacilitatorClient</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">FacilitatorClient</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> HttpClient http;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String baseUrl;

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> VerificationResponse <span class="hljs-title">verify</span><span class="hljs-params">(String paymentHeader, PaymentRequirements requirements)</span> 
        <span class="hljs-keyword">throws</span> Exception </span>{

        <span class="hljs-comment">// Construct verification request with payment and requirements</span>
        VerifyRequest body = VerifyRequest.builder()
            .paymentHeader(paymentHeader)     <span class="hljs-comment">// The X-PAYMENT header from client</span>
            .requirements(requirements)       <span class="hljs-comment">// What the server expects</span>
            .build();

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + <span class="hljs-string">"/verify"</span>))
            .POST(HttpRequest.BodyPublishers.ofString(Json.MAPPER.writeValueAsString(body)))
            .header(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>)
            .build();

        String json = http.send(request, HttpResponse.BodyHandlers.ofString()).body();
        <span class="hljs-keyword">return</span> Json.MAPPER.readValue(json, VerificationResponse.class);
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> SettlementResponse <span class="hljs-title">settle</span><span class="hljs-params">(String paymentHeader, PaymentRequirements requirements)</span> 
        <span class="hljs-keyword">throws</span> Exception </span>{

        <span class="hljs-comment">// Settlement happens after successful verification</span>
        SettleRequest body = SettleRequest.builder()
            .paymentHeader(paymentHeader)
            .requirements(requirements)
            .build();

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + <span class="hljs-string">"/settle"</span>))
            .POST(HttpRequest.BodyPublishers.ofString(Json.MAPPER.writeValueAsString(body)))
            .header(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>)
            .build();

        String json = http.send(request, HttpResponse.BodyHandlers.ofString()).body();
        <span class="hljs-keyword">return</span> Json.MAPPER.readValue(json, SettlementResponse.class);
    }
}
</code></pre>
<p>The facilitator checks several things:</p>
<ul>
<li><p>Is the signature valid?</p>
</li>
<li><p>Does the payment amount match the requirements?</p>
</li>
<li><p>Has this payment been used before?</p>
</li>
<li><p>Is the payment still within its expiration window?</p>
</li>
</ul>
<p>If verification passes, the facilitator also handles settlement by broadcasting the transaction to the blockchain of choice. The <code>FacilitatorClient</code> interface defines the contract that any facilitator client must implement:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">FacilitatorClient</span> </span>{
    <span class="hljs-function">VerificationResponse <span class="hljs-title">verify</span><span class="hljs-params">(String paymentHeader, PaymentRequirements requirements)</span></span>;
    <span class="hljs-function">SettlementResponse <span class="hljs-title">settle</span><span class="hljs-params">(String paymentHeader, PaymentRequirements requirements)</span></span>;
}
</code></pre>
<p>Your application needs to provide a concrete implementation of this interface.</p>
<h2 id="heading-integration-example">Integration example</h2>
<p>Now that we've seen the individual components (payment filters, facilitator integration, and client handling) let's look at how these pieces come together in a real application. Here's a minimal <code>Spring Boot</code> example that demonstrates the complete flow.</p>
<p>First, create a <code>@PaymentRequired</code> annotation:</p>
<pre><code class="lang-java"><span class="hljs-meta">@Target(ElementType.METHOD)</span>
<span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span>
<span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> PaymentRequired {
    <span class="hljs-function">String <span class="hljs-title">price</span><span class="hljs-params">()</span></span>;
    <span class="hljs-function">String <span class="hljs-title">currency</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> "USDC"</span>;
    <span class="hljs-function">String <span class="hljs-title">network</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> "base-sepolia"</span>;
}
</code></pre>
<p>Then modify the <code>PaymentFilter</code> to scan for these annotations at startup:</p>
<pre><code class="lang-java"><span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaymentFilter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Filter</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map&lt;String, BigInteger&gt; priceTable;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String payTo;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> FacilitatorClient facilitator;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PaymentFilter</span><span class="hljs-params">(ApplicationContext context, String payTo, FacilitatorClient facilitator)</span> </span>{
        <span class="hljs-keyword">this</span>.payTo = payTo;
        <span class="hljs-keyword">this</span>.facilitator = facilitator;
        <span class="hljs-keyword">this</span>.priceTable = buildPriceTableFromAnnotations(context);
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> Map&lt;String, BigInteger&gt; <span class="hljs-title">buildPriceTableFromAnnotations</span><span class="hljs-params">(ApplicationContext context)</span> </span>{
        Map&lt;String, BigInteger&gt; prices = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();

        <span class="hljs-comment">// Scan all @RestController beans for @PaymentRequired annotations</span>
        Map&lt;String, Object&gt; controllers = context.getBeansWithAnnotation(RestController.class);

        <span class="hljs-keyword">for</span> (Object controller : controllers.values()) {
            Method[] methods = controller.getClass().getMethods();
            <span class="hljs-keyword">for</span> (Method method : methods) {
                PaymentRequired payment = method.getAnnotation(PaymentRequired.class);
                <span class="hljs-keyword">if</span> (payment != <span class="hljs-keyword">null</span>) {
                    String path = extractPathFromMapping(method);
                    BigInteger amount = <span class="hljs-keyword">new</span> BigInteger(payment.price().replace(<span class="hljs-string">"."</span>, <span class="hljs-string">""</span>));
                    prices.put(path, amount);
                }
            }
        }
        <span class="hljs-keyword">return</span> prices;
    }
}
</code></pre>
<p>Now you can annotate your controller methods directly:</p>
<pre><code class="lang-java"><span class="hljs-meta">@RestController</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">WeatherController</span> </span>{

    <span class="hljs-meta">@GetMapping("/weather")</span>
    <span class="hljs-meta">@PaymentRequired(price = "0.001", currency = "USDC", network = "base-sepolia")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> WeatherData <span class="hljs-title">getWeather</span><span class="hljs-params">(<span class="hljs-meta">@RequestParam</span> String city)</span> </span>{
        <span class="hljs-comment">// Your existing business logic</span>
        <span class="hljs-keyword">return</span> weatherService.getWeatherForCity(city);
    }

    <span class="hljs-meta">@GetMapping("/premium-forecast")</span>
    <span class="hljs-meta">@PaymentRequired(price = "0.01", currency = "USDC", network = "base-sepolia")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ExtendedForecast <span class="hljs-title">getPremiumForecast</span><span class="hljs-params">(<span class="hljs-meta">@RequestParam</span> String city)</span> </span>{
        <span class="hljs-keyword">return</span> weatherService.getExtendedForecast(city);
    }
}

<span class="hljs-meta">@Configuration</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaymentConfig</span> </span>{

    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> PaymentFilter <span class="hljs-title">paymentFilter</span><span class="hljs-params">(ApplicationContext context)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> PaymentFilter(
            context,
            <span class="hljs-string">"&lt;WALLET_ADDRESS&gt;"</span>, <span class="hljs-comment">// Your wallet address</span>
            <span class="hljs-keyword">new</span> HttpFacilitatorClient(<span class="hljs-string">"&lt;FACILITATOR_URL&gt;"</span>)
        );
    }

    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> FilterRegistrationBean&lt;PaymentFilter&gt; <span class="hljs-title">paymentFilterRegistration</span><span class="hljs-params">(PaymentFilter filter)</span> </span>{
        FilterRegistrationBean&lt;PaymentFilter&gt; registration = <span class="hljs-keyword">new</span> FilterRegistrationBean&lt;&gt;();
        registration.setFilter(filter);
        registration.addUrlPatterns(<span class="hljs-string">"/*"</span>);
        registration.setOrder(<span class="hljs-number">1</span>);
        <span class="hljs-keyword">return</span> registration;
    }
}
</code></pre>
<p>The <code>@PaymentRequired</code> annotation handles pricing configuration declaratively, while the <code>PaymentFilter</code> automatically discovers these annotations at startup and builds the price table. Your existing business logic in the controller methods remains completely unchanged. The configuration wires everything together by registering the payment filter and connecting it to a facilitator service. Once deployed, requests to <code>/weather</code> cost 0.001 USDC and <code>/premium-forecast</code> costs 0.01 USDC, with all payment handling happening transparently at the HTTP layer.</p>
<h2 id="heading-security-and-production-considerations">Security and production considerations</h2>
<p><code>x402</code> is simple and elegant, it hides complexity. This is a pro and a con. It makes integration easy, but it also hides an important aspect: <strong>putting AI agents in charge of money creates new attack vectors</strong>.</p>
<p>While <code>EIP-712</code> signatures and nonce management handle replay attacks, what happens when an agent gets compromised? Traditional fraud detection relies on human behavioral patterns, but <strong>AI agents don't follow human spending habits</strong>. A compromised agent could drain funds faster than any human fraudster.</p>
<p>The facilitator component becomes another high-value target since it's verifying signatures and managing nonces. Unlike traditional payment processors that can reverse transactions, <strong>blockchain settlements are final</strong>.</p>
<p>Since <code>x402</code> is based on on-chain transactions, it inherits the operational risks of blockchain transactions. Gas fees fluctuate wildly, sometimes making micropayments economically inconvenient. Network congestion can delay transactions. What's an AI agent supposed to do when it needs real-time data but the payment is stuck in a mempool?</p>
<p>Another important aspect is regulation. Compliance varies across jurisdictions with different rules about automated payments, cryptocurrency usage, and data retention. An AI agent making a large volume of micro-transactions across borders might trigger AML alerts or violate local regulations without anyone realizing it.</p>
<h2 id="heading-whats-next">What's next</h2>
<p>What's interesting about <code>x402</code> is the timing. AI agents need autonomous payment capabilities, stablecoins provide a programmable money layer, and blockchain infrastructure has matured enough to handle scalable applications. These pieces haven't aligned before. Internet now needs a new form of payments, and traditional flows weren't built for autonomous agents or micropayments.</p>
<p><code>x402</code> is compelling thanks to its pragmatic approach. Instead of reinventing payments from scratch, it extends existing HTTP infrastructure. Instead of requiring new integrations, it works with standard patterns that we already understand.</p>
<p>The security and operational challenges are real, but they're engineering problems with possible solutions.</p>
<p>After nearly three decades, <code>HTTP 402</code> might finally do what it was designed for: make paying for things on the internet as simple as requesting them.</p>
<p><strong>The foundation is set. Now it's time to build.</strong></p>
<hr />
<p><em>Thanks</em> <a target="_blank" href="https://www.linkedin.com/in/erikreppel/"><em>Erik Reppel</em></a><em>,</em> <a target="_blank" href="https://www.linkedin.com/in/ronald-caspers-5403614a/"><em>Ronnie Caspers</em></a><em>,</em> <a target="_blank" href="https://www.linkedin.com/in/kevinleffew/"><em>Kevin Leffew</em></a><em>,</em> <a target="_blank" href="https://www.linkedin.com/in/dannyorgan/"><em>Danny Organ</em></a><em>, and all the team at</em> <a target="_blank" href="https://www.coinbase.com/"><em>Coinbase</em></a> <em>for open sourcing this protocol.</em></p>
<p><em>Thanks</em> <a target="_blank" href="https://www.linkedin.com/in/erikreppel/"><em>Erik Reppel</em></a> <em>and</em> <a target="_blank" href="https://www.linkedin.com/in/yuga-cohler/"><em>Yuga Cohler</em></a> <em>for reviewing</em> <a target="_blank" href="https://github.com/coinbase/x402/pull/178"><em>my contributions</em></a> <em>to</em> <code>x402</code>.</p>
<h2 id="heading-resources-and-further-reading">Resources and Further Reading</h2>
<ul>
<li><p><a target="_blank" href="https://tools.ietf.org/html/rfc2068#section-10.4.3?utm_source=blog.amorelli.tech">HTTP 402 Payment Required - RFC 2068</a> - Original 1997 HTTP specification</p>
</li>
<li><p><a target="_blank" href="https://x402.org/spec?utm_source=blog.amorelli.tech">x402 Protocol Specification</a> - Official protocol documentation</p>
</li>
<li><p><a target="_blank" href="https://github.com/coinbase/x402?utm_source=blog.amorelli.tech">x402 GitHub Repository</a> - Coinbase's open source implementation</p>
</li>
<li><p><a target="_blank" href="https://github.com/coinbase/x402/pull/178?utm_source=blog.amorelli.tech">x402 Java implementation</a> - The PR that introduced the Java implementation of the protocol</p>
</li>
<li><p><a target="_blank" href="https://eips.ethereum.org/EIPS/eip-712https://www.coinbase.com/developer-platform/discover/launches/x402?utm_source=blog.amorelli.tech">EIP-712: Ethereum Typed Structured Data Hashing and Signing</a> - Signing standard used in x402</p>
</li>
<li><p><a target="_blank" href="https://docs.base.org/?utm_source=blog.amorelli.tech">Base Network Documentation</a> - Layer 2 blockchain platform used in examples</p>
</li>
<li><p><a target="_blank" href="https://www.centre.io/usdc?utm_source=blog.amorelli.tech">USDC Documentation</a> - USD Coin stablecoin contract details</p>
</li>
<li><p><a target="_blank" href="https://docs.spring.io/spring-boot/docs/current/reference/html/web.html#web.servlet.embedded-container.servlets-filters-listeners?utm_source=blog.amorelli.tech">Spring Boot Filter Documentation</a> - Java middleware implementation</p>
</li>
<li><p><a target="_blank" href="https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html?utm_source=blog.amorelli.tech">Java HTTP Client API</a> - Client-side HTTP handling</p>
</li>
<li><p><a target="_blank" href="https://www.itu.int/en/ITU-T/studygroups/2017-2020/20/Pages/default.aspx?utm_source=blog.amorelli.tech">Machine-to-Machine (M2M) Communication Standards</a> - Autonomous system communication</p>
</li>
<li><p><a target="_blank" href="https://arxiv.org/abs/2308.11432?utm_source=blog.amorelli.tech">Autonomous AI Agent Architectures</a> - Research on AI agent design patterns</p>
</li>
<li><p><a target="_blank" href="https://research.google/pubs/an-introduction-to-googles-approach-for-secure-ai-agents/">Google’s Approach to Secure AI Agents</a> - Google propositions of a secure, human-guided AI agent framework</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[AI-Driven Refactoring in Large Scale Migrations: Strategies and Techniques.]]></title><description><![CDATA[A mountain of legacy code
Seven years ago, when Qonto was a new fintech with a handful of engineers and a single mission-critical web app, we chose Ember.js as our framework. In April 2016, the very first commit of what would become app.qonto.com lan...]]></description><link>https://blog.amorelli.tech/ai-driven-refactoring-in-large-scale-migrations-strategies-and-techniques</link><guid isPermaLink="true">https://blog.amorelli.tech/ai-driven-refactoring-in-large-scale-migrations-strategies-and-techniques</guid><category><![CDATA[AI]]></category><category><![CDATA[genai]]></category><category><![CDATA[llm]]></category><category><![CDATA[migration]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Stefano Amorelli]]></dc:creator><pubDate>Tue, 03 Jun 2025 21:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751230642386/4b38f427-cd65-4589-a51c-0247df816e8b.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-a-mountain-of-legacy-code">A mountain of legacy code</h2>
<p>Seven years ago, when Qonto was a new fintech with a handful of engineers and a single mission-critical web app, we chose <strong>Ember.js</strong> as our framework. In April 2016, the very first commit of what would become <code>app.qonto.com</code> landed in the repo, and Ember carried us through our explosive growth: from “just launched” to hundreds of thousands of customers, 30 deployments a day, and a <strong>rock-solid 93% test coverage</strong>.</p>
<p>Ember carried us through years of rapid product development and countless feature launches, but the innovation in software engineering never stops. Our product ambitions expanded, and so did the frameworks and technical stacks around us. To stay at the forefront of front-end development, in late 2023, we set a new heading, as detailed in our article <em>“</em><a target="_blank" href="https://medium.com/qonto-way/setting-sail-from-ember-why-we-are-charting-a-course-toward-react-at-qonto-8c475931cff2"><em>Setting sail from Ember: why we are charting a course toward React at Qonto</em></a><em>”</em>: “<strong>Let’s chart a course toward React.</strong>”</p>
<p>The decision felt energising, but it came with a price: <strong>migrating ~1 million lines of code from Ember to React.</strong></p>
<p>We estimated a software engineer to migrate roughly <strong>50 lines of code a day</strong> without sacrificing quality. At that pace, with enough engineers dedicated to this task, rewriting the whole app would stretch beyond <strong>two years of full-time effort</strong> (while we kept shipping new features on top of the old stack). And let’s be honest, migrating and refactoring code from one framework to the other is not exactly anyone’s idea of fun.</p>
<p>We needed a better route. One where AI and <code>codemods</code> automate most of the job, and engineers focus on the tricky edges.</p>
<blockquote>
<p><em>“In the age of AI, how can we speed up migrating a mountain of legacy code?”</em></p>
</blockquote>
<p>This post is about that journey: how we are approaching this project, what worked, what didn’t, and how AI transformed what seemed like an overwhelming migration into a clear and attainable task.</p>
<h3 id="heading-how-can-we-leverage-ai-to-do-this-for-us">“How can we leverage AI to do this for us?”</h3>
<p>In late January of this year, 2025, we launched an internal <a target="_blank" href="https://medium.com/qonto-way/aiming-high-how-kaizen-helped-us-turn-around-as-a-tech-team-1984ff008c0f">kaizen</a> (a focused, continuous improvement initiative). By that time, <em>C</em>laude 3.5 Sonnet by Anthropic had already demonstrated significant maturity as a model, especially given its strong performance on <code>SWE-Bench</code>, a benchmark evaluating foundation models’ capabilities in realistic coding tasks. Recognizing its potential, we asked ourselves: <em>how could we leverage this advanced LLM to automate substantial portions of our migration?</em></p>
<p>We set ambitious goals for ourselves: if an individual engineer managed to migrate ~50 LoC (lines of code) per day manually, maybe AI assistance could double that to 100 LoC/day; in this kaizen initiative, we aimed at doubling it again to <strong>200 LoC/day.</strong></p>
<p>This digit felt bold at the time.</p>
<p>Little did we know we would blow past this target by orders of magnitude!</p>
<h2 id="heading-first-experiment-a-quick-win-with-rag">First experiment: a quick win with RAG</h2>
<p>Every journey starts small. Our first experiment was to build a quick prototype, a web-based AI assistant that could help us convert Ember code to React on a case-by-case basis. We started with a Retrieval-Augmented Generation (<strong>RAG</strong>) approach using an AI agent fed with our internal data sources, including our monorepo on GitHub and our knowledge base.</p>
<p>The result of this first iteration was a web-based chatbot that we could feed Ember code to and ask for the equivalent React code. We augmented it with a refined system prompt, coding guidelines, and snippet examples for context.</p>
<p>To our delight, this chatbot showed promising results! With proper prompting and some back and forth, it could output acceptable React components from Ember inputs. This was a big morale boost, the concept was sound.</p>
<p>However, this was just the beginning.</p>
<p>The bot worked well in a sandbox, but it wasn’t yet integrated into our development workflow. It still required engineers to context switch between the web interface of the chatbot and their editor, and manually prompt each time they wanted to convert code from Ember to React.</p>
<p>We wanted to go further: to have AI directly working on our actual codebase, ideally making changes that were automatically committed with little supervision.</p>
<p>The question now became: <em>how do we turn this into a more automated, practical engineering tool?</em></p>
<h2 id="heading-hackathon-to-production-building-an-ai-powered-cli">Hackathon to production: building an AI-powered CLI</h2>
<p>A few weeks in, at the beginning of February 2025, armed with confidence from the prototype, we organised a one-day internal hackathon to level up the idea.</p>
<p>Our small team (engineers from various squads who were passionate and curious about AI) got together with a mission: to <strong>enable an LLM to do the migration for us</strong>.</p>
<p>In a single day, we brainstormed, coded, and demoed an MVP CLI tool that automatically refactored Ember components to React. It was a lot of trial and error, analysing already-migrated code to spot patterns, crafting prompts based on Ember-React diffs and our internal guidelines, and evaluating different tools and LLMs.</p>
<p>By the end of the hack day, we had a rough but working <strong>CLI agent</strong>. This script could take an Ember component file and generate a React version, end-to-end.</p>
<p>At the core of the script, we picked <a target="_blank" href="https://github.com/Aider-AI/aider"><code>aider</code></a> an open-source command-line tool designed to automatically apply code changes generated by LLMs based on a prompt. It offers built-in support for multiple models and easy to plug it in using <code>AWS Bedrock</code> as a provider. We chose it because being a CLI we could quickly script it, customise prompts, iterate, and experiment across different models and inputs, all within our existing development workflow.</p>
<p>Here’s an overview of how it works:</p>
<p><img src="https://miro.medium.com/v2/resize:fit:1313/1*A7pEcOFFyOGSyEXelCzY5Q.png" alt /></p>
<p>General overview of our CLI agent using <code>aider</code> and Anthropic’s Claude via AWS Bedrock</p>
<p>In simple terms, the CLI automates the whole refactoring process:</p>
<ol>
<li><p><strong>Select code &amp; branch creation:</strong> We choose a target component (via a fuzzy finder) and the tool spins up a new git branch for that migration. This keeps changes isolated and reviewable.</p>
</li>
<li><p><strong>AI Pass 1, Ember to React:</strong> <code>aider</code> calls our LLM (<code>claude-3–5-sonnet</code>, Anthropic’s latest model at that time, via <code>AWS Bedrock</code>) with a carefully crafted prompt. The prompt includes the Ember component code as input and asks for the equivalent React code, while following our coding guidelines and style conventions. <code>aider</code> streams the AI’s suggested changes and applies them to the code-base as an initial diff.</p>
</li>
<li><p><code>codemod</code> <strong>adjustments:</strong> Next, we run a custom <code>codemod</code> (<code>react-bridge-migrator</code>) on the output. This <code>codemod</code> handles mechanical tasks and fine-grained fixes. Think of things like adjusting imports, hooking the component into our React app framework, and other repetitive changes that are easier to script deterministically.</p>
</li>
<li><p><strong>AI Pass 2, review and polish:</strong> With the <code>codemod</code> adjustments in place, we then invoke <code>aider</code> + <code>claude</code> <strong>again</strong> to review the combined diff (original Ember vs. new React) and refine the result. In this second pass, <strong>the engineer can pair program interactively in natural language with the LLM</strong> to fix any inconsistencies, apply final touches, or handle pieces the <code>codemod</code> didn’t cover.</p>
</li>
<li><p><strong>Automatic commit:</strong> If all goes well, the CLI then automatically creates a <code>git commit</code> with the changes (Ember component replaced by the new React component). The developer’s role shifts from writing boilerplate to reviewing the automatically migrated output, a much more efficient use of time!</p>
</li>
</ol>
<p>Throughout this process, the LLM is doing the heavy lifting of code generation.</p>
<p>We experimented with different models and at that time found that <code>claude</code> hit a sweet spot of quality and context length for our needs, and <code>aider</code> made it easy to feed multiple files into the prompt (e.g., "already migrated" examples and style guides) and to iteratively refine the output.</p>
<p>In effect, we built an <strong>AI pair programmer,</strong> embedded in our CLI, that knew our guidelines, our code-base, and could directly migrate Ember code to React with little supervision.</p>
<h2 id="heading-results-high-velocity">Results: high-velocity</h2>
<p><img src="https://miro.medium.com/v2/resize:fit:1313/1*QlYEQGyQ_GXvUkNtsi9DVg.png" alt /></p>
<p><em>Velocity of migration (lines of code converted per week). After introducing the AI-driven CLI. Our actual migration velocity (blue line) surged far above the original projected pace (red line).</em></p>
<p>With the initial version of the agent complete, we began a two-week evaluation period starting February 10th, 2025. During this time, we closely monitored the lines of code migrated with the agent: <strong>the results exceeded all expectations</strong>. Our migration throughput skyrocketed. We went from ~50 LoC/day per engineer to <strong>hundreds of lines</strong> per day, sometimes even breaking the <strong>1,000 LoC/day per engineer.</strong> The weekly velocity chart above tells the story: once the AI agent came online, the blue line (actual migrated code per week) shoots up like a rocket, leaving the modest linear projection (red line) in the dust.</p>
<p>In concrete terms, at peak performance, the tool delivered about <em>20 times</em> the output of manual coding.</p>
<blockquote>
<p>That’s roughly a <strong>2,000% gain in productivity over our original estimation!</strong></p>
</blockquote>
<p>Even we had trouble believing these numbers at first! What used to take days now takes hours or minutes. <strong>In the span of two weeks, one dedicated engineer,</strong> with marginal assistance from a teammate, <strong>successfully migrated 8,632 lines of code using the agent.</strong></p>
<p>Equally important: <strong>quality and consistency</strong> remained high. Since the AI was adopting our own code patterns (as we fed it examples of already-migrated components and guidelines), the React code it produced was in line with our expectations: while not every AI suggestion was perfect the first time, a human-in-the-loop review filled the gap.</p>
<p>Notably, this performance level was already attained using <code>claude-3–5-sonnet</code>, and as new models were released, we observed a significant improvement when upgrading to <code>claude-3–7-sonnet</code> when released on February 24th, 2025, and most recently to the latest <code>claude-4-sonnet</code> released on May 22nd of 2025.</p>
<h2 id="heading-kaizen-in-action-small-team-big-impact">Kaizen in action: small team, big impact</h2>
<p>It’s worth highlighting how crucial the <strong><em>kaizen</em> approach</strong> was to this effort. We treated this project as a series of small, iterative improvements rather than a big top-down initiative. A tiny task force (just a small team of motivated engineers) took the idea and ran with it in a hackathon-style blitz. This gave us speed and creative freedom. We weren’t afraid to try something experimental.</p>
<p>After the initial hackathon success, we continued in <strong>iterative cycles</strong>: brainstorm, prototype, test, repeat. Each iteration taught us something that fed into the next. This lean approach is classic k<em>aizen:</em> <strong>making continuous small improvements that compound into significant gains</strong>. In our case, this mindset helped us to quickly navigate uncertainties in prompt engineering and tool integration. Instead of getting stuck in analysis paralysis, we built a proof of concept, evaluated impact, and then doubled down on what worked; as a result, we moved fast.</p>
<h2 id="heading-what-can-you-learn-from-our-experiment">What can you learn from our experiment?</h2>
<p>Using AI to refactor such a large codebase in production taught us valuable lessons:</p>
<ul>
<li><p><strong>The importance of an extensive test suite:</strong> At Qonto, we are proud of a comprehensive test suite that, between integration, unit, and acceptance tests, covers <strong>93%</strong> of our code. In a very large refactoring, having a solid testing foundation allows you to move fast and with much more confidence, knowing that regressions would be caught by the tests.</p>
</li>
<li><p><strong>Prompt engineering is the key:</strong> the quality of the AI’s output is highly dependent on the prompt. Being explicit in the instructions results in much higher quality output. For example, our prompt was not a simple “convert this Ember component to React” but contained a detailed structure containing our coding guidelines (function component style, hooks usage, etc.) and hand-picked examples of Ember-to-React transformations, with dos and don’ts and best practices.</p>
</li>
<li><p><strong>Human-in-the-loop:</strong> As much as the AI helps us move faster, it does not replace the need for experienced human judgment. Having a software engineer in the loop to validate and run the application ensures quality. The AI got us 90% of the way there, and we, engineers, handled the rest.</p>
</li>
<li><p><strong>AI evolves fast:</strong> During our experimentation period of only a few months, we witnessed significant developments, including major LLM releases such as <code>claude-3–7-sonnet</code> and <code>claude-4-sonnet</code>, along with the introduction of new powerful tools like <code>Claude Code</code>. Now more than ever it’s fundamental to maintain short feedback loops, continuously update our knowledge, and remain open to emerging technologies.</p>
</li>
<li><p><strong>Tooling:</strong> <code>aider</code> was instrumental as an open-source CLI because it allowed flexibility during our initiative.</p>
</li>
</ul>
<h2 id="heading-challenges-and-next-steps">Challenges and next steps</h2>
<blockquote>
<p><em>“So, what’s next for the AI migration agent?”</em></p>
</blockquote>
<p>Short answer: <strong>we’re evolving it</strong>.</p>
<p>While the CLI agent delivered massive wins during our kaizen, this is not the final form. We quickly learned that, as powerful as it was, a CLI agent in the shape of a CLI script didn’t quite fit the daily habits of most front-end engineers. We had to ask ourselves: <strong>what’s the most natural environment for developers to use this kind of tooling?</strong></p>
<p>That led us to a new direction, <strong>a VS Code extension that is integrated within developers’ IDE</strong>.</p>
<h2 id="heading-what-didnt-work">What didn’t work</h2>
<ul>
<li><p>The <strong>CLI agent was a</strong> <code>bash</code><strong>script</strong>, and while that kept things simple for a quick prototype, it started to show limits. There was little room for scalability, debugging prompts were clunky, and it wasn’t tightly integrated with the editor.</p>
</li>
<li><p><strong>Front-end engineers preferred staying inside their IDE</strong>, not context-switching between a terminal and their editor. An integrated extension makes the experience more fluid and ergonomic.</p>
</li>
<li><p>The <strong>prompt architecture needed rethinking</strong>. Initially, we put everything (guidelines, examples, diffs) into one huge markdown prompt. This made it hard to maintain and even harder to debug.</p>
</li>
<li><p>We’ve since learned that <strong>splitting the prompt into modular files</strong> makes it easier to reason about and easier for teammates to refine and contribute. It also helps the LLM process the input better: the separation creates a clearer semantic structure and reduces noise.</p>
</li>
</ul>
<p>So the next step in this journey? We’re keeping up to date with the latest trends of agentic engineering, and we’re actively developing new tools to help our engineers. For example, we’re developing a dev extension that brings the power of our AI agent closer to where engineers work. We’re keeping the heart of what made the CLI great, automation, prompts, and AI pair programming, but wrapping it in a smoother, more accessible interface.</p>
<h2 id="heading-conclusion-adopting-ai-with-a-continuous-improvement-mindset">Conclusion: adopting AI with a continuous improvement mindset</h2>
<p>What started as an experiment ended up fundamentally accelerating a critical migration for us. We leveraged AI and, with a true kaizen mindset, we turned a huge project into an opportunity to innovate and learn.</p>
<p><strong>AI is extremely powerful, and the question is, how best can you leverage it?</strong> In our case, we applied it to augment our engineers, automating extremely time-intensive tasks at a scale and speed that still impresses us.</p>
<p>This journey also reminded us of the value of <em>exploring new approaches</em> and pushing the boundaries of our engineering workflows.</p>
<p>It’s easy to stick to tried-and-true methods, but a small, passionate team with a bold idea can deliver outsized results with new and innovative approaches!</p>
]]></content:encoded></item></channel></rss>