<?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" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Henry Ly]]></title><description><![CDATA[Co-Founder & CTO at Adamo Software | 12+ years of experiences]]></description><link>https://henrylyadamo.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!Hu8z!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaf094c8-ed22-4214-81ff-444d30b0d51b_1305x1305.png</url><title>Henry Ly</title><link>https://henrylyadamo.substack.com</link></image><generator>Substack</generator><lastBuildDate>Tue, 16 Jun 2026 02:27:12 GMT</lastBuildDate><atom:link href="https://henrylyadamo.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Henry Ly]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[henrylyadamo@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[henrylyadamo@substack.com]]></itunes:email><itunes:name><![CDATA[Henry Ly]]></itunes:name></itunes:owner><itunes:author><![CDATA[Henry Ly]]></itunes:author><googleplay:owner><![CDATA[henrylyadamo@substack.com]]></googleplay:owner><googleplay:email><![CDATA[henrylyadamo@substack.com]]></googleplay:email><googleplay:author><![CDATA[Henry Ly]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Why most travel platforms struggle to scale during peak seasons]]></title><description><![CDATA[Travel platform (source: Unsplash)]]></description><link>https://henrylyadamo.substack.com/p/why-most-travel-platforms-struggle</link><guid isPermaLink="false">https://henrylyadamo.substack.com/p/why-most-travel-platforms-struggle</guid><dc:creator><![CDATA[Henry Ly]]></dc:creator><pubDate>Fri, 12 Jun 2026 08:01:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!4W0T!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e5376bc-e1b1-40a1-9457-8f3b5ea6c8d5_1024x682.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4W0T!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e5376bc-e1b1-40a1-9457-8f3b5ea6c8d5_1024x682.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4W0T!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e5376bc-e1b1-40a1-9457-8f3b5ea6c8d5_1024x682.jpeg 424w, https://substackcdn.com/image/fetch/$s_!4W0T!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e5376bc-e1b1-40a1-9457-8f3b5ea6c8d5_1024x682.jpeg 848w, https://substackcdn.com/image/fetch/$s_!4W0T!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e5376bc-e1b1-40a1-9457-8f3b5ea6c8d5_1024x682.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!4W0T!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e5376bc-e1b1-40a1-9457-8f3b5ea6c8d5_1024x682.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4W0T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e5376bc-e1b1-40a1-9457-8f3b5ea6c8d5_1024x682.jpeg" width="1024" height="682" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0e5376bc-e1b1-40a1-9457-8f3b5ea6c8d5_1024x682.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:682,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:58854,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://henrylyadamo.substack.com/i/201712985?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e5376bc-e1b1-40a1-9457-8f3b5ea6c8d5_1024x682.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4W0T!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e5376bc-e1b1-40a1-9457-8f3b5ea6c8d5_1024x682.jpeg 424w, https://substackcdn.com/image/fetch/$s_!4W0T!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e5376bc-e1b1-40a1-9457-8f3b5ea6c8d5_1024x682.jpeg 848w, https://substackcdn.com/image/fetch/$s_!4W0T!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e5376bc-e1b1-40a1-9457-8f3b5ea6c8d5_1024x682.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!4W0T!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e5376bc-e1b1-40a1-9457-8f3b5ea6c8d5_1024x682.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="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"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p style="text-align: center;"><em>Travel platform (source: Unsplash) </em></p><p>After years of helping travel businesses build and scale booking platforms, mobile travel applications, and digital travel ecosystems, I&#8217;ve noticed a recurring pattern: most travel platforms don&#8217;t struggle during peak seasons because of traffic alone. More often, the root cause lies in architectural decisions made long before the first surge in demand.</p><p>A platform may perform flawlessly under normal conditions for most of the year. However, when seasonal events drive a sudden increase in user activity, hidden scalability issues quickly surface. Search performance degrades, third-party integrations become unstable, booking confirmations slow down, and customer experience suffers.</p><p>In our experience, peak seasons rarely create problems; they simply expose the weaknesses that already exist within a platform&#8217;s architecture. Understanding these challenges is the first step toward building travel systems that can scale reliably as demand grows.</p><h1><strong>The real scaling challenges in travel platforms</strong></h1><h2><strong>01. Search traffic grows much faster than booking volume</strong></h2><p>One of the most overlooked realities of travel platforms is that search traffic scales much faster than actual bookings. Before making a purchase, travelers typically compare multiple destinations, departure dates, hotels, room types, and flight options. As a result, a single booking may generate dozens, or even hundreds, of search requests.</p><p>This means the search layer often becomes the first bottleneck during peak seasons. If the platform relies heavily on real-time queries to databases or third-party suppliers, response times can increase dramatically as traffic surges.</p><p>The result is slower search experiences, higher bounce rates, and ultimately lost bookings before users even reach the checkout stage.</p><h2><strong>02. Heavy dependence on third-party travel APIs</strong></h2><p>Unlike many digital products, travel platforms rarely operate in isolation. They depend on a complex ecosystem of external providers, including GDSs, hotel wholesalers, payment gateways, airline systems, PMSs, and channel managers.</p><p>During high-demand periods, these third-party services may experience rate limits, slower response times, or temporary outages. Even if a platform&#8217;s own infrastructure remains stable, performance can still be affected by external dependencies.</p><p>This creates a unique scalability challenge: the platform&#8217;s reliability is only as strong as the weakest integration in its ecosystem.</p><h2><strong>03. Real-time inventory synchronization</strong></h2><p>Travel inventory is highly dynamic. Hotel rooms, airline seats, tour slots, and transportation availability can change within seconds, especially during peak booking periods.</p><p>As traffic increases, multiple users may attempt to book the same inventory simultaneously. Without proper synchronization mechanisms, platforms can encounter issues such as overselling, duplicate bookings, or inaccurate availability displays.</p><p>Maintaining inventory consistency while supporting thousands of concurrent transactions requires far more sophisticated architecture than traditional content-driven applications.</p><h1><strong>How modern travel platforms scale</strong></h1><p>Leading travel platforms do not scale by simply adding more servers. Instead, they redesign their architecture to handle the unique characteristics of travel workloads.</p><h2><strong>01. Decoupling search from booking</strong></h2><p>In many legacy systems, search requests, bookings, payments, and customer data all rely on the same database. As traffic grows, search queries consume a significant portion of system resources and impact transactional operations.</p><p>Modern travel platforms typically use dedicated search infrastructure, such as Elasticsearch, OpenSearch, or Redis-powered caching layers, to handle large search volumes independently. This ensures that even during peak seasons, users can search smoothly without affecting booking performance.</p><h2><strong>02. Leverage event-driven architecture</strong></h2><p>Rather than processing interconnected processes (booking confirmations, payment, inventory updates, notifications, loyalty programs, analytics) synchronously, modern systems use event-driven architecture with technologies such as Kafka, RabbitMQ, or cloud messaging services. When a booking is completed, an event is published, and downstream services process their tasks independently.</p><p>This approach reduces system bottlenecks, improves resilience, and allows the platform to handle significantly higher transaction volumes.</p><h2><strong>03. Implementing intelligent caching</strong></h2><p>Not every request requires real-time processing. Destination pages, hotel information, popular search results, and travel content often change less frequently than booking transactions.</p><p>To reduce pressure on databases and third-party APIs, scalable travel platforms leverage multiple caching layers, including CDN caching, application caching, and distributed caching technologies such as Redis. This allows the system to serve frequently requested content with minimal latency while reducing infrastructure costs.</p><h1><strong>What does this mean when building a travel software platform</strong></h1><p>As travel businesses grow, they are not only serving more users. They are managing more searches, more inventory updates, more supplier integrations, more personalization, and more operational dependencies. Each layer adds new challenges that cannot be solved by infrastructure alone.</p><p>The travel platforms that thrive during peak seasons are rarely the ones with the largest engineering teams. They are the ones who anticipate growth early and build architectures designed for change.</p><p>In our experience, peak seasons don&#8217;t create scalability problems; they reveal them. The sooner travel companies recognize this, the better prepared they will be for sustainable growth in an increasingly competitive market.</p>]]></content:encoded></item><item><title><![CDATA[Why I think every engineering team needs a 'Boring Layer']]></title><description><![CDATA[And why we keep trying to delete ours, even though we know better.]]></description><link>https://henrylyadamo.substack.com/p/why-i-think-every-engineering-team</link><guid isPermaLink="false">https://henrylyadamo.substack.com/p/why-i-think-every-engineering-team</guid><dc:creator><![CDATA[Henry Ly]]></dc:creator><pubDate>Tue, 05 May 2026 03:57:39 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1635944095210-23114a1fb7c0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHxpY2ViZXJnJTIwdW5kZXJ3YXRlcnxlbnwwfHx8fDE3Nzc5NTMyNTJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1635944095210-23114a1fb7c0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHxpY2ViZXJnJTIwdW5kZXJ3YXRlcnxlbnwwfHx8fDE3Nzc5NTMyNTJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1635944095210-23114a1fb7c0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHxpY2ViZXJnJTIwdW5kZXJ3YXRlcnxlbnwwfHx8fDE3Nzc5NTMyNTJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1635944095210-23114a1fb7c0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHxpY2ViZXJnJTIwdW5kZXJ3YXRlcnxlbnwwfHx8fDE3Nzc5NTMyNTJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1635944095210-23114a1fb7c0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHxpY2ViZXJnJTIwdW5kZXJ3YXRlcnxlbnwwfHx8fDE3Nzc5NTMyNTJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1635944095210-23114a1fb7c0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHxpY2ViZXJnJTIwdW5kZXJ3YXRlcnxlbnwwfHx8fDE3Nzc5NTMyNTJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1635944095210-23114a1fb7c0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHxpY2ViZXJnJTIwdW5kZXJ3YXRlcnxlbnwwfHx8fDE3Nzc5NTMyNTJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="3000" height="4000" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1635944095210-23114a1fb7c0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHxpY2ViZXJnJTIwdW5kZXJ3YXRlcnxlbnwwfHx8fDE3Nzc5NTMyNTJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4000,&quot;width&quot;:3000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;a large iceberg floating in the water&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="a large iceberg floating in the water" title="a large iceberg floating in the water" srcset="https://images.unsplash.com/photo-1635944095210-23114a1fb7c0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHxpY2ViZXJnJTIwdW5kZXJ3YXRlcnxlbnwwfHx8fDE3Nzc5NTMyNTJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1635944095210-23114a1fb7c0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHxpY2ViZXJnJTIwdW5kZXJ3YXRlcnxlbnwwfHx8fDE3Nzc5NTMyNTJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1635944095210-23114a1fb7c0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHxpY2ViZXJnJTIwdW5kZXJ3YXRlcnxlbnwwfHx8fDE3Nzc5NTMyNTJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1635944095210-23114a1fb7c0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHxpY2ViZXJnJTIwdW5kZXJ3YXRlcnxlbnwwfHx8fDE3Nzc5NTMyNTJ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="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"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@simonppt">SIMON LEE</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p></p><p>A few weeks ago I caught one of our senior engineers staring at a 200-line idempotency validation module like it had personally insulted him. He had spent the morning trying to ship a feature, and this module kept rejecting his test bookings because of an edge case he had not anticipated. He turned to me and said, half-joking, &#8220;I want to delete this whole file.&#8221;</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://henrylyadamo.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>I told him I have wanted to delete that file at least three times this year. So has every senior engineer on the team. We never have. We never will. And the reason we keep wanting to is the entire point of this essay.</p><p>There is a layer in mature engineering systems that I have started calling the <strong>Boring Layer</strong>. It is the unglamorous infrastructure that protects you from your own mistakes, from your customers&#8217; mistakes, from your suppliers&#8217; mistakes, and from the universe&#8217;s general tendency toward entropy. Idempotency keys. Validation pipelines. Observability instrumentation. Circuit breakers. Retry budgets. The Boring Layer.</p><p>Every team I have worked with eventually realizes they need it. Most teams under-invest in it for years before admitting that. And nearly every team, once it is built, periodically tries to delete it.</p><p>This essay is about why.</p><div><hr></div><h2>The shape of the Boring Layer</h2><p>Before getting into the leadership pattern, let me describe what I mean concretely.</p><p>If you map our booking platform&#8217;s components by how often each one gets touched in active feature development versus how often it gets touched in production incidents, you get a clean inversion. The features people are excited to build (recommendations, conversational booking, dynamic pricing) are touched constantly during development and rarely during incidents. The Boring Layer (deduplication, validation, retry logic, observability) is barely touched during development and shows up in nearly every incident postmortem.</p><p><em><strong>The Boring Layer&#8217;s job is to be invisible when things work and indispensable when they do not.</strong></em> That is its actual function: a system that lives quietly underneath everything and absorbs the failure modes you did not predict.</p><p>A non-exhaustive inventory of what lives in the Boring Layer:</p><ul><li><p>Request idempotency keys that prevent duplicate bookings when a user clicks twice</p></li><li><p>Cross-service trace context propagation so you can debug a failure six services deep</p></li><li><p>Validation pipelines that catch bad parameters at the boundary instead of letting them reach external APIs</p></li><li><p>Hallucination guards that block LLM-generated facts that are not supported by source data</p></li><li><p>Rate limiters that protect downstream services from your own retry storms</p></li><li><p>Tail-based sampling logic that decides which traces are worth keeping</p></li><li><p>Schema versioning that lets you change inventory formats without breaking downstream consumers</p></li><li><p>Circuit breakers that fail fast when a supplier API is down instead of cascading failure</p></li></ul><p>If you have built a production system at any reasonable scale, you have written some of these. If you have not built them, you are accumulating debt that will become visible the first time something fails in a way you did not predict.</p><div><hr></div><h2>Why the Boring Layer is constantly under threat</h2><p>Here is the thing that took me years to understand: the Boring Layer is not just under-invested in. It is actively under attack, often by the same people who built it.</p><p>Three forces work against it:</p><p><strong>Force 1: It feels like it is not doing anything.</strong> When the Boring Layer works correctly, you cannot see it. There is no flashy metric. There is no demo to show stakeholders. The dashboard for &#8220;duplicate bookings prevented&#8221; reads zero, because the Boring Layer prevented them all. To a casual observer (or a junior engineer, or an impatient PM), the system looks like it is doing nothing. It is doing everything. That is the problem.</p><p><strong>Force 2: It slows down the part of work that feels productive.</strong> Every feature ticket has to engage with the Boring Layer at some point. The new endpoint needs idempotency. The new tool call needs validation. The new service needs trace propagation. Each of these is a 30-minute task at minimum, and it does not produce visible feature progress. If your engineers are measured on shipped features, the Boring Layer is exactly the thing they will resent.</p><p><strong>Force 3: Its failure modes are slow and silent.</strong> The Boring Layer protects against problems that compound. A duplicate booking today is one customer complaint. A duplicate booking that goes undetected for six months because there was no idempotency check is a class-action lawsuit risk. The pain is asymmetric in time. Skipping the Boring Layer feels free this quarter and catastrophic in two years. Most engineering organizations are structured to optimize for this quarter.</p><p>Combined, these three forces mean that the Boring Layer is constantly being questioned, deprioritized, or actively dismantled by teams that need to ship faster. I have watched promising young companies enter a death spiral that started with &#8220;we don&#8217;t need all this defensive code, it&#8217;s slowing us down.&#8221;</p><div><hr></div><h2>The leadership pattern I have settled on</h2><p>After enough years of watching this dynamic play out, I have settled on a few principles for protecting the Boring Layer without becoming the engineering equivalent of a building inspector.</p><p><strong>Make the Boring Layer&#8217;s value visible without making it the focus.</strong> When something fails in production and the Boring Layer caught it, write that up in the postmortem with explicit attribution: &#8220;the validation layer rejected this request before it reached Stripe; without that check, we would have charged the customer twice.&#8221; This is not chest-thumping. It is calibration. Engineers who only see the Boring Layer when it gets in their way will resent it. Engineers who also see it catching real failures develop an appreciation for it.</p><p><strong>Treat Boring Layer work as feature work, not as overhead.</strong> When we estimate stories, the Boring Layer touch points get explicit time. &#8220;Add idempotency: 4 hours. Add tracing instrumentation: 2 hours. Add validation: 3 hours.&#8221; This makes the cost legible to PMs and engineers, and it removes the temptation to skip it under pressure. <em><strong>The thing that does not appear on the estimate is the thing that gets cut.</strong></em></p><p><strong>Resist the temptation to &#8220;simplify&#8221; the Boring Layer.</strong> A senior engineer will eventually look at a piece of the Boring Layer and propose a simpler version. Sometimes they are right. Often they are looking at the simplified version of a system that learned its complexity through painful production lessons. Before approving any simplification, ask: &#8220;what was the bug that caused the original implementation to be this way?&#8221; If nobody on the team can answer, that is a sign you are about to remove a load-bearing wall.</p><p><strong>Make the Boring Layer look as good as the rest of the codebase.</strong> Bad Boring Layer code gets deleted. Good Boring Layer code survives. Refactors, tests, documentation, naming. This part of the codebase deserves the same craftsmanship as the customer-facing features. The fact that it is not visible to users is not a license to half-ass it.</p><p><strong>Protect the people who maintain it.</strong> Some engineers are temperamentally drawn to defensive engineering. They like the puzzle of &#8220;what could go wrong here?&#8221; These engineers are gold. They get bored on flashy feature work and shine on idempotency and validation. Build their careers around this. Promote them based on incidents prevented, not features shipped.</p><div><hr></div><h2>The deletion impulse never fully goes away</h2><p>This is the part I want to be honest about: even on a mature team that has felt the pain of insufficient defensive engineering, the impulse to delete the Boring Layer never fully goes away.</p><p>Last quarter we had a sprint where everything we tried to ship got blocked or slowed by some piece of validation, retry logic, or observability instrumentation. By Friday, two of our most experienced engineers were openly proposing that we rip out our hallucination validation pipeline because it was rejecting too many &#8220;obviously fine&#8221; responses from the LLM and forcing regenerations.</p><p>They were not wrong that the pipeline was being annoying. They were wrong that it should be deleted.</p><p>The conversation we had instead: what is the false-positive rate, what is the false-negative rate, what would happen if we tuned the threshold instead of removing the pipeline, what is the cost of a hallucinated booking detail reaching a customer versus the cost of a regeneration? We tuned the threshold. The annoyance dropped. The pipeline stayed.</p><p>This is the loop I have started to expect. Periodically, smart engineers under pressure will propose removing a piece of the Boring Layer because it is in their way today. The right response is not to defend the layer reflexively. It is to engage with the specific complaint, distinguish &#8220;this is poorly tuned&#8221; from &#8220;this is no longer needed,&#8221; and adjust accordingly. Most of the time, the answer is tuning. Some of the time, the answer is genuine deletion (the system has evolved past the original need). Rarely, the answer is &#8220;yes, this is critical, do not touch it.&#8221; All three answers require taking the complaint seriously rather than dismissing it.</p><div><hr></div><h2>Why this matters more in the AI era</h2><p>A footnote that turned out to be the most important part of this essay.</p><p>When I started thinking about the Boring Layer, I was thinking about traditional distributed systems: idempotency, retries, validation, tracing. Standard SRE-flavored stuff.</p><p>The arrival of LLM-based features has dramatically expanded what lives in the Boring Layer. Hallucination validation pipelines. Tool-use call deduplication. Cost circuit breakers that prevent runaway agent loops. Confidence thresholds for autonomous actions. Memory architectures that prevent context drift across sessions.</p><p>Every one of these is a new layer of defensive engineering that LLM features need but their developers tend to under-invest in. The classic mistake is shipping a system that works on the demo path and discovering in production that it loops forever, hallucinates prices, or spends $50,000 in API calls before anyone notices.</p><p>There is now an entire generation of &#8220;AI features&#8221; being built without an AI Boring Layer underneath them. The companies that will win the next phase of LLM-powered software are not the ones with the most clever prompts. They are the ones whose AI Boring Layer is robust enough that the clever stuff can fail safely.</p><p>If you are leading an engineering team that ships AI features, the leverage right now is not in the model or the prompt. It is in everything underneath. The validation pipelines, the cost ceilings, the deduplication, the confidence thresholds. The Boring Layer for AI is being figured out in real time by every team that has hit a production incident and realized that &#8220;the model did something weird&#8221; is not a usable explanation for an angry customer.</p><div><hr></div><h2>What I tell new senior engineers</h2><p>When I onboard a senior engineer to our team, one of the first things I tell them is: the most important code you will write here is code you will never demo. It will not impress anyone in a sprint review. It will not show up on your performance evaluation as a flashy launch. It will quietly prevent disasters that nobody will ever know about, and your reward will be that you sleep at night.</p><p>Some engineers hear that and visibly relax. They are the ones I want owning the Boring Layer.</p><p>Some engineers hear that and look concerned. That is fine too. They are probably better suited to the customer-facing parts of the codebase, and we need those people too. But they should know that the senior engineer building idempotency keys is not doing junior work. They are doing some of the most consequential work in the company. They just do not get to brag about it on Twitter.</p><div><hr></div><h2>A final thought on the deletion impulse</h2><p>I started this essay with the engineer who wanted to delete the idempotency validation file. I want to come back to him.</p><p>He did not delete it. We talked through the edge case he was hitting, found that it was a legitimate gap in the validation logic (not a problem with the validation existing), fixed it, and moved on. He told me later that he was actually glad we had the file, because the edge case he found would have been a serious bug in production.</p><p>This is the version of the Boring Layer&#8217;s value that I think gets missed. It is not just protection against failure. It is a forcing function for thinking carefully about edge cases. Building defensive infrastructure is a way of compressing institutional learning into code. Every weird edge case that ever happened in production becomes a check in the validation pipeline. Every supplier API quirk becomes a normalization rule. Every category of LLM hallucination becomes a check in the validation layer.</p><p><em><strong>A team without a Boring Layer has to remember its lessons. A team with a Boring Layer has its lessons baked into the system</strong></em>. The first kind of team forgets things over time. The second kind compounds knowledge.</p><p>If you are leading an engineering team and you have not invested in your Boring Layer, this is the leverage. Not the next feature. The infrastructure underneath the next feature.</p><div><hr></div><p><em>I&#8217;m CTO at <a href="https://adamosoft.com/">Adamo Software</a>. I write occasionally about engineering leadership, AI tooling infrastructure, and what I learn running software delivery for international clients out of Vietnam.</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://henrylyadamo.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Why your AI coding assistant plateaus after week one]]></title><description><![CDATA[The compounding problem nobody is solving, and the memory architecture that fixes it.]]></description><link>https://henrylyadamo.substack.com/p/why-your-ai-coding-assistant-plateaus</link><guid isPermaLink="false">https://henrylyadamo.substack.com/p/why-your-ai-coding-assistant-plateaus</guid><dc:creator><![CDATA[Henry Ly]]></dc:creator><pubDate>Tue, 28 Apr 2026 08:40:55 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1775994121052-e66d295832a0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHxhaSUyMGNvZGluZyUyMGFzc2lzdGFudHxlbnwwfHx8fDE3NzczNjU1MzR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1775994121052-e66d295832a0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHxhaSUyMGNvZGluZyUyMGFzc2lzdGFudHxlbnwwfHx8fDE3NzczNjU1MzR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1775994121052-e66d295832a0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHxhaSUyMGNvZGluZyUyMGFzc2lzdGFudHxlbnwwfHx8fDE3NzczNjU1MzR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1775994121052-e66d295832a0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHxhaSUyMGNvZGluZyUyMGFzc2lzdGFudHxlbnwwfHx8fDE3NzczNjU1MzR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1775994121052-e66d295832a0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHxhaSUyMGNvZGluZyUyMGFzc2lzdGFudHxlbnwwfHx8fDE3NzczNjU1MzR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1775994121052-e66d295832a0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHxhaSUyMGNvZGluZyUyMGFzc2lzdGFudHxlbnwwfHx8fDE3NzczNjU1MzR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1775994121052-e66d295832a0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHxhaSUyMGNvZGluZyUyMGFzc2lzdGFudHxlbnwwfHx8fDE3NzczNjU1MzR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="4313" height="3726" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1775994121052-e66d295832a0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHxhaSUyMGNvZGluZyUyMGFzc2lzdGFudHxlbnwwfHx8fDE3NzczNjU1MzR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3726,&quot;width&quot;:4313,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Claude code vibe coding diagram with text&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Claude code vibe coding diagram with text" title="Claude code vibe coding diagram with text" srcset="https://images.unsplash.com/photo-1775994121052-e66d295832a0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHxhaSUyMGNvZGluZyUyMGFzc2lzdGFudHxlbnwwfHx8fDE3NzczNjU1MzR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1775994121052-e66d295832a0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHxhaSUyMGNvZGluZyUyMGFzc2lzdGFudHxlbnwwfHx8fDE3NzczNjU1MzR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1775994121052-e66d295832a0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHxhaSUyMGNvZGluZyUyMGFzc2lzdGFudHxlbnwwfHx8fDE3NzczNjU1MzR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1775994121052-e66d295832a0?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzfHxhaSUyMGNvZGluZyUyMGFzc2lzdGFudHxlbnwwfHx8fDE3NzczNjU1MzR8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="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"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@hdbernd">Bernd &#128247; Dittrich</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p></p><p>Every developer using AI coding assistants seriously hits the same wall eventually: <strong>the AI keeps making the same mistakes you&#8217;ve already corrected</strong>. Hydration bugs you fixed last month. JWT config you debugged for hours. Architectural decisions you carefully made and explained. All gone the next session.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://henrylyadamo.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>I spent the past few weeks designing a memory architecture to solve this for our team&#8217;s Claude Code workflow. The thinking process turned out to be more interesting than the final artifact. What follows is the full reasoning: what I tried, what failed, and the principles that ended up working.</p><p>If you&#8217;re using AI coding tools on anything bigger than a weekend project, this is worth your time.</p><h2>The real problem isn&#8217;t memory. It&#8217;s compounding.</h2><p>Most people frame this as &#8220;AI doesn&#8217;t have memory between sessions.&#8221; That&#8217;s the symptom, not the problem.</p><p>The actual problem is that <strong>knowledge doesn&#8217;t compound</strong>. A senior engineer doesn&#8217;t just remember bugs. They recognize patterns. Same symptom, different surface. Same root cause, different stack trace. That pattern recognition is what separates a five-year engineer from a one-year engineer doing the same task five times.</p><p>If your AI assistant can&#8217;t compound knowledge across sessions, it permanently operates at &#8220;fresh hire on day one&#8221; level. Forever.</p><p>There&#8217;s a useful analogy here from how teams actually scale. When a senior engineer leaves, the cost isn&#8217;t the lines of code they wrote. It&#8217;s the unwritten knowledge: the bugs they remember, the architectural choices they made and the reasons behind them, the gotchas in the data pipeline that aren&#8217;t documented anywhere. Onboarding a new hire takes weeks not because reading code is hard, but because compounding knowledge takes time to transfer.</p><p>Your AI assistant is in a permanent state of pre-onboarding. Every session.</p><p>The math gets ugly fast. If a typical mid-sized client project takes six months and produces, say, 200 reusable lessons that a senior engineer would internalize, your AI is fundamentally re-debugging the same 200 issues across 100+ sessions. Multiply that across multiple concurrent projects in an IT services context, and you&#8217;re looking at thousands of repeated bug classes per quarter, every one of them billable hours that compound nothing.</p><h2>My first instinct was wrong</h2><p>My initial design: one big <code>Learnings.md</code> file. Every time I solve something tricky, append it. Load it at session start.</p><p>It looks clean. It works for two weeks. Then it falls apart for three reasons.</p><p><strong>Token cost.</strong> A 5,000-line learnings file loaded at every session means I&#8217;m burning context on 4,900 lines that have nothing to do with my current task. Modern models have generous context windows, but the relationship between context size and reasoning quality isn&#8217;t linear. More noise in context degrades output, and you&#8217;re paying API costs on every wasted token.</p><p><strong>Signal-to-noise.</strong> The more I capture, the harder it gets to find the right lesson. Paradoxically, a more &#8220;complete&#8221; knowledge base becomes less useful. This is the same reason most internal wikis fail: not because they&#8217;re too sparse, but because once they cross a complexity threshold, finding the right entry costs more than re-deriving the answer.</p><p><strong>Internal contradictions.</strong> Six months in, you have lessons that conflict. &#8220;Use pattern A&#8221; on line 200, &#8220;stop using pattern A&#8221; on line 1,800. Which is current? The AI doesn&#8217;t know. And it has no mechanism to resolve precedence: both lessons are equally weighted, equally available.</p><p>This is why most &#8220;auto-memory&#8221; systems people build degrade after two to three months. The structure can&#8217;t hold up.</p><h2>The architectural shift that fixed it</h2><p>Three principles changed the design.</p><p><strong>1. Progressive disclosure.</strong> Don&#8217;t load everything. Load an index. Let the index route to the specific lesson needed for the current task.</p><p>This is the same principle behind how humans use reference material. You don&#8217;t memorize an entire codebase to make a change. You grep for the relevant function, read the surrounding 50 lines, and act. Your AI&#8217;s knowledge architecture should work the same way: a tiny, always-loaded index that knows what exists; full content loaded only when relevance is established.</p><p>In practice: a 50-line <code>_index.md</code> file at session start, with cards loaded individually only when the recall phase identifies a match. We cap loaded cards at five per session. Token cost stays nearly flat regardless of how big the knowledge base grows.</p><p><strong>2. Symptom-first indexing, not topic-first.</strong> This was the single highest-leverage decision in the whole design.</p><p>Bugs recur at the <em>symptom</em> level, not the topic level. A developer hitting &#8220;Hydration mismatch&#8221; in the console searches for &#8220;hydration mismatch&#8221;, not for &#8220;Next.js rendering patterns.&#8221; If your index is organized by topic, the developer (or the AI) won&#8217;t find the right lesson even when it exists.</p><p>We tag every lesson with a <code>symptom:</code> prefix containing the literal error message keyword or observable behavior:</p><ul><li><p><code>symptom:hydration-mismatch</code></p></li><li><p><code>symptom:401-cross-service</code></p></li><li><p><code>symptom:json-malformed-with-fence</code></p></li><li><p><code>symptom:agent-loops-forever</code></p></li></ul><p>When the AI hits a symptom in the current session, the recall phase matches on these tags first, before falling back to topic tags. The match rate jumped dramatically once we made this change. In the topic-only version of the system, recall was finding relevant lessons maybe 40% of the time. After symptom-first tagging, that climbed to around 80% on bugs that had been previously captured.</p><p><strong>3. Confidence + lifecycle, not append-only.</strong> Lessons should have a confidence score that moves over time. A lesson contradicted by new evidence loses confidence. A lesson unused for 90 days gets archived, not deleted. Hard-deleting loses history; never-archiving creates noise.</p><p>This was the hardest principle to internalize. My first design had no concept of stale knowledge. Everything captured stayed forever, equally weighted. That&#8217;s not how human memory works, and it&#8217;s not how a knowledge system should work either.</p><p>We use a 0.0 to 0.9 confidence range. New lessons start at 0.5 (bug fix), 0.7 (user correction), or 0.3 (retrospective). Confirmed-useful lessons go up; contradicted lessons go down. At 0.0, the lesson moves to an archive folder, still findable but excluded from normal recall.</p><h2>The override I had to add: human-confirmed capture</h2><p>The best existing pattern I studied uses <strong>auto-capture</strong>: when the AI detects a lesson worth keeping, it writes to disk automatically.</p><p>I rejected this for one reason: <strong>AI auto-capture overproduces noise.</strong></p><p>When Claude Code decides &#8220;this is worth capturing,&#8221; it&#8217;s right maybe 60% of the time. The other 40% is: things that look like patterns but aren&#8217;t, things that are obvious framework behavior, things that are too session-specific to generalize. Auto-write means your <code>lessons/</code> folder fills up with mediocre cards, and the high-quality ones get drowned.</p><p>So I changed it: every capture shows me the proposed card and asks <code>y/n/edit</code>. Three keystrokes. The friction is intentional. It forces a moment of &#8220;is this actually a lesson, or am I just feeling productive?&#8221;</p><p>After two weeks of use, my reject rate settled around 30%. That&#8217;s 30% of noise I&#8217;d otherwise be carrying. Compounded over six months, the difference between a curated knowledge base and an auto-captured one is roughly the difference between a usable tool and an unusable one.</p><p>A note on this: friction is usually treated as bad UX, but here it&#8217;s load-bearing. Every confirmation step is also a learning moment for the human. You start recognizing what counts as a real lesson versus what doesn&#8217;t, which improves your capture intuition over time. Auto-capture removes that feedback loop entirely.</p><h2>What separates a &#8220;lesson&#8221; from &#8220;a thing that happened&#8221;</h2><p>Designing the capture criteria forced me to articulate something I&#8217;d never written down. A real lesson has all four:</p><ul><li><p>A <strong>trigger</strong> I can recognize next time (the symptom)</p></li><li><p>A <strong>root cause</strong> different from what the symptom suggests</p></li><li><p>A <strong>prevention rule</strong> specific enough to apply, general enough to reuse</p></li><li><p>A <strong>detection signal</strong> I can check before the bug bites</p></li></ul><p>If a &#8220;lesson&#8221; is missing any of these, it&#8217;s just a war story. Useful for retrospectives, useless for future debugging.</p><p>This rubric alone has changed how I write commit messages and PR descriptions, even outside the AI context. It&#8217;s also become a useful frame for PR reviews. When reviewing someone else&#8217;s fix for a non-trivial bug, asking &#8220;what&#8217;s the trigger / root cause / prevention rule / detection signal?&#8221; surfaces a category of issues that get fixed in the immediate code but never captured as institutional knowledge.</p><p>Most &#8220;lessons learned&#8221; documents in engineering teams fail this test. They describe what happened (war story), not how to recognize it next time (lesson). The two look similar on paper. They&#8217;re completely different in utility.</p><h2>What I&#8217;d do differently if I started over</h2><p>A few things I&#8217;d front-load instead of discovering through use.</p><p><strong>Symptom tags are the whole game.</strong> I almost designed this skill with topic-only tags. That would have failed silently. The cards would exist, recall just wouldn&#8217;t find them. Symptom-first tagging was the single highest-leverage decision. If you build something like this, start there, not with the storage format or the confidence math.</p><p><strong>Empty is the right starting state.</strong> Don&#8217;t seed the system with example lessons. Empty cards from real bugs are worth more than 20 plausible-looking seeded ones. The latter creates phantom patterns the AI starts pattern-matching against. We seeded our first version with five &#8220;common Next.js issues&#8221; cards we wrote up from memory, and within a week the AI was confidently applying lessons to situations that didn&#8217;t quite fit, because the seeds were too generic.</p><p><strong>Consolidation isn&#8217;t optional.</strong> I originally designed this without a quarterly cleanup phase. Within a month I had near-duplicate cards, conflicting cards, and stale cards. Periodic consolidation isn&#8217;t a &#8220;nice to have&#8221;. It&#8217;s the difference between a knowledge system that compounds and one that decays.</p><p>The consolidation pass takes about 30 minutes per quarter for a project. You read through the cards, merge duplicates, archive things that no longer apply, surface conflicts for explicit resolution. Tiny investment. Massive impact on signal quality.</p><p><strong>Treat the index as a product, not a side effect.</strong> The auto-generated <code>_index.md</code> is what determines whether recall actually works. Spending time on its structure (column ordering, sorting logic, whether to include symptom tags inline) has more impact on system quality than anything in the cards themselves.</p><h2>Why this compounds harder for IT services than for product teams</h2><p>There&#8217;s a non-obvious second-order effect worth naming, especially for anyone running engineering at an IT services firm or agency.</p><p>For a product team, a memory architecture compounds knowledge within one codebase. Useful, but bounded.</p><p>For a services firm running multiple concurrent client engagements, the same architecture compounds across clients, even when their stacks differ. Why? Because <em>symptom patterns repeat across stacks</em>. The shape of &#8220;JWT misconfigured between services&#8221; looks structurally identical whether the stack is .NET + FastAPI, Node + Go, or Python + Java. The symptom tag matches; the lesson applies; the engineer ramps faster on a problem they&#8217;ve technically never seen before.</p><p>We&#8217;re seeing this play out across our client work. A lesson captured during a Next.js engagement transfers cleanly to a Vue project six weeks later, because the underlying symptom (hydration-style mismatch from runtime values in initial render) is framework-agnostic. The .NET-FastAPI lesson on env var naming applies to literally any cross-service auth setup.</p><p>This means the leverage isn&#8217;t linear in the number of projects. It&#8217;s roughly quadratic. Lesson density per project &#215; number of projects &#215; cross-applicability rate. For a five-engagement portfolio, that compounds into a body of institutional knowledge that&#8217;s worth real money in won deals and reduced delivery risk.</p><p>I haven&#8217;t seen this written about anywhere. The &#8220;AI memory&#8221; conversation is dominated by product-team perspectives, where the framing is about a single product&#8217;s lifecycle. The services-firm angle is structurally different and, I&#8217;d argue, where the highest leverage actually lives.</p><h2>The broader lesson about building with AI</h2><p>The thing I keep coming back to: <strong>AI tools don&#8217;t fail because they&#8217;re not smart enough. They fail because their environment doesn&#8217;t compound.</strong></p><p>We obsess over prompts, models, context windows. There&#8217;s a whole content economy around prompt engineering tips and &#8220;the best Claude prompts&#8221; listicles. But the highest-leverage work is often the boring infrastructure underneath: how knowledge persists, how it gets retrieved, how it ages out, how it gets validated.</p><p>A mediocre AI with a great memory architecture will outperform a great AI with no memory architecture, on any long-running project. The compounding curve is just that powerful.</p><p>The model improvements you&#8217;re going to see over the next two years will be incremental. The infrastructure improvements you build into your AI workflow can be exponential. Engineering leaders should be spending at least as much time thinking about the second category as the first.</p><h2>Where to start</h2><p>If this resonates and you want to build something similar, start small.</p><p>Add a <code>docs/lessons/</code> folder to your most active project. Don&#8217;t worry about the skill framework or the validation rules yet. Just commit to capturing lessons in a structured format (the four-part rubric is a fine starting point) and tagging them with symptom keywords from the actual error messages.</p><p>Manually load relevant cards into your AI assistant&#8217;s context when starting a related task. Yes, manually. The automation comes later. The discipline of recall, actually pulling up past lessons and feeding them to the AI before asking for help, is what builds the muscle. Once you&#8217;ve felt the difference between a session with relevant lessons loaded and one without, the rest of the architecture starts designing itself.</p><p>After a month, you&#8217;ll have between 15 and 40 cards, a clear sense of which symptom tags are actually useful, and enough operational experience to know what to automate. <em>Then</em> invest in tooling.</p><p>Most knowledge systems fail because they&#8217;re designed before the team has any data on what they actually need. Build the messy, manual version first. Let the structure emerge from real use.</p><p>If you&#8217;re leading engineering at scale and haven&#8217;t built a memory architecture around your AI tooling yet, that&#8217;s where the leverage is. Not in the model. Not in the prompt. In the infrastructure underneath.</p><p>I&#8217;d be curious to hear what other teams are building in this space. The pattern feels under-discussed for how high-leverage it is. If you&#8217;re working on something similar, or if you&#8217;ve tried and hit different walls than the ones I described, I&#8217;d genuinely like to compare notes.</p><div><hr></div><p><em>I'm CTO at Adamo Software. I write occasionally about engineering leadership, AI tooling infrastructure, and what I learn running software delivery for international clients out of Vietnam.</em></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://henrylyadamo.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The travel app everyone wants to build is not the one anyone will pay for]]></title><description><![CDATA[Every travel startup that has walked into our office in the past eighteen months has a version of the same pitch.]]></description><link>https://henrylyadamo.substack.com/p/the-travel-app-everyone-wants-to</link><guid isPermaLink="false">https://henrylyadamo.substack.com/p/the-travel-app-everyone-wants-to</guid><dc:creator><![CDATA[Henry Ly]]></dc:creator><pubDate>Tue, 21 Apr 2026 08:34:15 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1737113476280-6d149e86f989?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0OHx8dHJhdmVsJTIwbW9iaWxlJTIwYXBwfGVufDB8fHx8MTc3Njc2MDI2MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1737113476280-6d149e86f989?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0OHx8dHJhdmVsJTIwbW9iaWxlJTIwYXBwfGVufDB8fHx8MTc3Njc2MDI2MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1737113476280-6d149e86f989?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0OHx8dHJhdmVsJTIwbW9iaWxlJTIwYXBwfGVufDB8fHx8MTc3Njc2MDI2MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1737113476280-6d149e86f989?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0OHx8dHJhdmVsJTIwbW9iaWxlJTIwYXBwfGVufDB8fHx8MTc3Njc2MDI2MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1737113476280-6d149e86f989?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0OHx8dHJhdmVsJTIwbW9iaWxlJTIwYXBwfGVufDB8fHx8MTc3Njc2MDI2MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1737113476280-6d149e86f989?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0OHx8dHJhdmVsJTIwbW9iaWxlJTIwYXBwfGVufDB8fHx8MTc3Njc2MDI2MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1737113476280-6d149e86f989?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0OHx8dHJhdmVsJTIwbW9iaWxlJTIwYXBwfGVufDB8fHx8MTc3Njc2MDI2MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="2139" height="3803" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1737113476280-6d149e86f989?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0OHx8dHJhdmVsJTIwbW9iaWxlJTIwYXBwfGVufDB8fHx8MTc3Njc2MDI2MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3803,&quot;width&quot;:2139,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A person holding up a cell phone in front of a bridge&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A person holding up a cell phone in front of a bridge" title="A person holding up a cell phone in front of a bridge" srcset="https://images.unsplash.com/photo-1737113476280-6d149e86f989?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0OHx8dHJhdmVsJTIwbW9iaWxlJTIwYXBwfGVufDB8fHx8MTc3Njc2MDI2MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1737113476280-6d149e86f989?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0OHx8dHJhdmVsJTIwbW9iaWxlJTIwYXBwfGVufDB8fHx8MTc3Njc2MDI2MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1737113476280-6d149e86f989?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0OHx8dHJhdmVsJTIwbW9iaWxlJTIwYXBwfGVufDB8fHx8MTc3Njc2MDI2MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1737113476280-6d149e86f989?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw0OHx8dHJhdmVsJTIwbW9iaWxlJTIwYXBwfGVufDB8fHx8MTc3Njc2MDI2MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="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"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@cardmapr">CardMapr.nl</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>Every travel startup that has walked into our office in the past eighteen months has a version of the same pitch. An AI concierge. Personalized itineraries. Natural language trip planning. Conversational booking.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://henrylyadamo.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>And every one of them, without exception, has shown me a demo where they type &#8220;plan me a 5-day trip to Japan&#8221; into a chat box and watch GPT-4 generate a plausible-looking itinerary.</p><p>I have stopped being polite about this. The AI trip planner is the most over-indexed feature in travel tech right now, and almost nobody has figured out how to make it convert into revenue. I have watched teams spend six to twelve months building sophisticated AI planners, launch them, and discover that their users use the feature once, enjoy it, and then never book anything.</p><p>I want to write about why this keeps happening, what we have seen actually work across the travel platforms we have built, and what I now tell founders when they come to us with an AI itinerary idea.</p><h2>The demo is not the product</h2><p>Here is what a travel AI demo looks like:</p><p>User types a fuzzy request. Model generates a beautiful multi-day itinerary with restaurant recommendations, transit notes, and curated experiences. Founder gets excited. Investor gets excited. Article gets written in TechCrunch.</p><p>Here is what the product experience looks like four weeks after launch:</p><p>User types a fuzzy request. Model generates a beautiful itinerary. User reads it. User thinks &#8220;cool.&#8221; User closes the tab. User books their actual trip on Booking.com, Agoda, or Expedia because those places have prices, availability, photos they trust, reviews they trust, and a cancellation policy they understand.</p><p>The gap between these two experiences is where most AI travel products die. The demo optimizes for &#8220;wow, the AI is smart.&#8221; The product needs to optimize for &#8220;I am about to spend $3,000 and I need to be sure.&#8221;</p><p>These are not the same problem. Being impressive and being trustworthy are actually in tension. A confident AI narrator telling you about the &#8220;hidden gems&#8221; of Kyoto feels magical in a demo and vaguely sketchy when you are about to book non-refundable tickets.</p><h2>What we have actually seen work</h2><p>Across the travel platforms we have built in the last few years, including <a href="https://adamosoft.com/travel-and-hospitality-software-development/">B2B aggregators handling 2M+ hotel properties, sports travel platforms serving 65K events across 240 cities, and several direct-to-consumer booking engines</a>, the AI features that show up in retention data are not the ones founders expect.</p><p>The three that consistently earn their keep:</p><p><strong>1. AI for friction removal, not inspiration.</strong> The best-performing AI feature we have shipped is not a planner. It is a deal-alert agent that watches prices on specific routes the user cares about and notifies them when something moves. Unsexy. Users love it. Conversion to booking is measurable.</p><p><strong>2. AI as a search parser, not a search replacer.</strong> Users do not want to chat their way to a hotel. They want to type &#8220;beachfront Bali under 3M VND&#8221; and see filtered results instantly. We use LLMs to parse natural language into structured search parameters, then hand off to a traditional search UI with maps, photos, and filters. The LLM disappears after parsing. This converts. Full chat-based booking does not.</p><p><strong>3. AI for post-booking operations.</strong> Re-accommodation during flight disruptions. Dynamic itinerary updates when a tour gets cancelled. Automated refund eligibility checks. These are boring, operational AI uses. They have real ROI because they replace expensive human support work. None of them demo well.</p><p>The pattern here is not subtle. AI wins when it removes a specific, repeated friction. AI loses when it tries to replace the trust infrastructure that OTAs have spent twenty years building.</p><h2>Why founders keep building the wrong thing</h2><p>I have theories on why this keeps happening.</p><p>First, the demo environment is seductive. GPT-4 and Claude can produce incredibly plausible travel content. When you are building the prototype, you are both the developer and the only user. You never test whether you would actually book a $4,000 honeymoon based on your own AI&#8217;s recommendation. Your users will.</p><p>Second, founders are not representative users. Most travel founders are frequent travelers, comfortable with new tools, willing to experiment. The user who actually drives volume in travel is the once-or-twice-a-year vacation planner who is anxious about spending money and wants social proof, not an AI opinion.</p><p>Third, there is a category error about what AI is good at. LLMs are excellent at generating plausible text. They are not, by default, excellent at making decisions where the user needs to feel certain. Travel booking is a high-certainty purchase. People read reviews for hours. They cross-reference. They are not looking for a confident narrator.</p><p>Fourth, the VC narrative rewards AI-forward pitches right now. If you raise a round by waving around an AI trip planner demo, you are incentivized to keep building that thing even when your retention data is screaming at you to pivot.</p><h2>What I tell founders now</h2><p>When a travel founder comes to us with an AI planner idea, I ask two questions before we talk architecture.</p><p>&#8220;What does conversion look like, and how do you know the AI caused it?&#8221;</p><p>Most cannot answer this. They have DAU numbers. They have session length. They do not have a clean funnel from AI interaction to booking revenue, and they have not thought about what attribution would look like. If you cannot articulate how the AI feature drives measurable revenue, you are building a toy.</p><p>&#8220;If you removed the AI tomorrow, which users would complain?&#8221;</p><p>This is the sharper question. For the best features we have built, the answer is clear and small: &#8220;the users who use our price alerts&#8221; or &#8220;the travel agents who use our itinerary parser to save time.&#8221; For failed AI features, the answer is &#8220;nobody, really.&#8221;</p><p>If nobody will complain when the AI goes away, you did not build a product feature. You built a marketing asset.</p><h2>The honest middle path</h2><p>I am not saying AI has no place in travel tech. It clearly does. We have shipped AI components in travel platforms that I believe in and that drive real outcomes.</p><p>I am saying the conversational AI trip planner, the thing most founders are building, is the least interesting and least defensible use of AI in this industry. The interesting work is in the operational layer: pricing intelligence, disruption management, fraud detection, personalization inside traditional UIs, dynamic packaging that responds to supplier inventory signals in real time.</p><p>None of these demo well on stage. All of them affect revenue.</p><p>If you are thinking about building an AI travel product, my suggestion is to start by mapping where your users currently experience the most friction. Not where you think AI would be cool. Where real money or real time is being lost to a broken workflow. Build the AI there.</p><p>That is where the customers who will pay you actually live.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://henrylyadamo.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Most AI features in travel apps are solving the wrong problem]]></title><description><![CDATA[The industry is racing to add AI to everything. But the features getting shipped aren&#8217;t matching the problems users actually have.]]></description><link>https://henrylyadamo.substack.com/p/most-ai-features-in-travel-apps-are</link><guid isPermaLink="false">https://henrylyadamo.substack.com/p/most-ai-features-in-travel-apps-are</guid><dc:creator><![CDATA[Henry Ly]]></dc:creator><pubDate>Tue, 14 Apr 2026 01:57:25 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1762330907387-d4aa874dfb03?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxhaSUyMGZlYXR1cmVzfGVufDB8fHx8MTc3NTYzMzAyNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1762330907387-d4aa874dfb03?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxhaSUyMGZlYXR1cmVzfGVufDB8fHx8MTc3NTYzMzAyNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1762330907387-d4aa874dfb03?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxhaSUyMGZlYXR1cmVzfGVufDB8fHx8MTc3NTYzMzAyNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1762330907387-d4aa874dfb03?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxhaSUyMGZlYXR1cmVzfGVufDB8fHx8MTc3NTYzMzAyNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1762330907387-d4aa874dfb03?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxhaSUyMGZlYXR1cmVzfGVufDB8fHx8MTc3NTYzMzAyNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1762330907387-d4aa874dfb03?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxhaSUyMGZlYXR1cmVzfGVufDB8fHx8MTc3NTYzMzAyNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1762330907387-d4aa874dfb03?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxhaSUyMGZlYXR1cmVzfGVufDB8fHx8MTc3NTYzMzAyNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="6000" height="4000" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1762330907387-d4aa874dfb03?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxhaSUyMGZlYXR1cmVzfGVufDB8fHx8MTc3NTYzMzAyNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4000,&quot;width&quot;:6000,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Microsoft edge app displayed on smartphone screen&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Microsoft edge app displayed on smartphone screen" title="Microsoft edge app displayed on smartphone screen" srcset="https://images.unsplash.com/photo-1762330907387-d4aa874dfb03?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxhaSUyMGZlYXR1cmVzfGVufDB8fHx8MTc3NTYzMzAyNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1762330907387-d4aa874dfb03?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxhaSUyMGZlYXR1cmVzfGVufDB8fHx8MTc3NTYzMzAyNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1762330907387-d4aa874dfb03?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxhaSUyMGZlYXR1cmVzfGVufDB8fHx8MTc3NTYzMzAyNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1762330907387-d4aa874dfb03?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxhaSUyMGZlYXR1cmVzfGVufDB8fHx8MTc3NTYzMzAyNnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="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"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@zulfugarkarimov">Zulfugar Karimov</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>I have spent the last two years leading engineering at <a href="https://adamosoft.com/">Adamo Software</a>, building AI-powered features for travel platforms. Recommendation engines, conversational booking assistants, dynamic pricing models, predictive disruption alerts. Some of them worked. Some of them did not. The ones that failed had one thing in common: they were solving a problem that sounded impressive in a pitch deck but did not exist in the user's actual experience.</p><p>This is not a product management problem. It is an engineering leadership problem. When a CTO greenlights an AI feature without interrogating whether the underlying system even needs intelligence, the team spends months building something sophisticated on top of something broken.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://henrylyadamo.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>The pattern I keep seeing</h2><p>A travel company wants &#8220;AI-powered search.&#8221; The pitch is compelling: travelers type natural language queries, the AI understands intent, and results are personalized in real time. The demo looks incredible.</p><p>Then you look at the actual search infrastructure. The database is not indexed for the queries users actually make. Availability data is cached for 30 minutes, so results are frequently stale. Filters do not work properly on mobile. The search is slow not because it lacks intelligence, but because the underlying data pipeline was never optimized for speed.</p><p>Adding an LLM on top of this does not make search better. It makes it more expensive and harder to debug. The AI generates beautiful natural language responses based on stale inventory data. The user sees a recommended hotel, clicks through, and discovers it is sold out. The experience is worse than a simple filter-based search that at least shows accurate availability.</p><p>I have seen this pattern three times in the last year alone. Each time, the fix was not more AI. It was fixing the data layer underneath.</p><h2>Where AI actually moves the needle in travel</h2><p>This is not an argument against AI in travel. It is an argument for precision about where AI creates value versus where it creates complexity.</p><p>After building and measuring AI features across multiple travel platforms, these are the three areas where AI consistently delivers measurable impact.</p><p><strong>Dynamic pricing.</strong> Hotel and flight pricing involves hundreds of variables: demand patterns, competitor rates, local events, weather, booking pace, cancellation rates. No human revenue manager can process all of these signals simultaneously and update rates across 6 OTA channels in real time. AI pricing engines can. The platforms we have worked on report 7-12% revenue uplift after deploying AI-driven dynamic pricing. This is the clearest ROI case for AI in travel because the problem is genuinely too complex for manual solutions.</p><p><strong>Disruption management.</strong> When a flight is cancelled, the traveler needs rebooking options immediately. The system needs to check alternative flights, hotel availability near the new departure point, ground transport options, and the traveler&#8217;s preferences, all within seconds. An AI agent that can query multiple systems, evaluate tradeoffs, and present a rebooking plan is solving a real problem that cannot be solved with a static rule engine. The key difference from the search example: disruption management involves genuine multi-step reasoning under time pressure with incomplete information. That is exactly what LLMs are good at.</p><p><strong>Post-booking personalization.</strong> Not recommendation engines on the homepage. Those rarely move conversion in meaningful ways. What works is personalization after the traveler has already booked: suggesting restaurants near their hotel based on their dining preferences, alerting them about events happening during their stay, offering upgrade options at the right moment. This works because the system has rich context (confirmed dates, destination, traveler history) and the suggestions are actionable, not speculative.</p><h2>Where teams waste months</h2><p>And these are the areas where I see engineering teams burn quarters of work for minimal return.</p><p><strong>AI-powered search on broken data.</strong> As described above. If your availability data is stale, your filters are broken, or your database is not indexed for the queries users make, adding an LLM layer will not fix the experience. Fix the data first. If search is still a problem after the data layer is solid, then evaluate whether AI adds value.</p><p><strong>Chatbots that replace help documentation.</strong> A travel company builds a chatbot to answer customer questions. The chatbot is trained on the same FAQ content that was already on the website. Users now get the same answers through a chat interface instead of a help page. The interaction feels more modern, but the resolution rate is identical. Meanwhile, the chatbot hallucinated a cancellation policy twice last month, which the static help page would never do. The problem was never &#8220;users cannot get answers.&#8221; The problem was &#8220;users cannot find the help page.&#8221; That is a navigation problem, not an AI problem.</p><p><strong>Recommendation engines with no behavioral data.</strong> A new travel app launches with an AI recommendation engine. The problem: the app has 3,000 users and minimal booking history. Collaborative filtering needs density to work. Content-based recommendations need well-structured property data. Without either, the &#8220;AI recommendations&#8221; are essentially random suggestions with a personalization label. A curated editorial list would outperform the algorithm at this stage and cost nothing to maintain.</p><h2>The question I now ask before greenlighting any AI feature</h2><p>After enough expensive lessons, I developed a simple filter. Before any AI feature enters our sprint backlog, I ask the engineering lead one question:</p><p><em>If we fixed the non-AI version of this experience, would users still need intelligence?</em></p><p>If the answer is no, we fix the underlying system first. If the answer is yes, we build AI. If the answer is &#8220;we don&#8217;t know,&#8221; we run a two-week experiment with the simplest possible version before committing engineering resources.</p><p>This filter has killed about 40% of AI feature proposals in the last year. Every one of them was replaced by a simpler fix that shipped faster and performed better.</p><h2>What this means for how we build travel platforms</h2><p>The companies that will win in travel tech over the next three years are not the ones adding AI to every feature. They are the ones building <a href="https://adamosoft.com/travel-and-hospitality-software-development/">travel and hospitality platforms</a> where the foundational systems, data pipelines, search infrastructure, payment processing, inventory sync, work well enough that AI can amplify them rather than compensate for them.</p><p>AI on top of a solid foundation is a multiplier. AI on top of a broken foundation is a distraction with a higher cloud bill.</p><p>I will be writing more about the specific engineering decisions behind building travel and enterprise platforms. What worked, what did not, and what I would do differently. If that is useful to you, subscribe and I will send it to your inbox.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://henrylyadamo.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Three currencies, two suppliers, one booking: what we learned building payment infrastructure for travel]]></title><description><![CDATA[We lost $2,100 a month on FX spreads and 14% of European transactions before we figured out what travel payments actually require.]]></description><link>https://henrylyadamo.substack.com/p/three-currencies-two-suppliers-one</link><guid isPermaLink="false">https://henrylyadamo.substack.com/p/three-currencies-two-suppliers-one</guid><dc:creator><![CDATA[Henry Ly]]></dc:creator><pubDate>Wed, 08 Apr 2026 06:50:04 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1721378466942-4f83ff2a84ef?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5fHx0cmF2ZWwlMjBwYXltZW50fGVufDB8fHx8MTc3NTYzMDk1MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1721378466942-4f83ff2a84ef?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5fHx0cmF2ZWwlMjBwYXltZW50fGVufDB8fHx8MTc3NTYzMDk1MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1721378466942-4f83ff2a84ef?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5fHx0cmF2ZWwlMjBwYXltZW50fGVufDB8fHx8MTc3NTYzMDk1MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1721378466942-4f83ff2a84ef?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5fHx0cmF2ZWwlMjBwYXltZW50fGVufDB8fHx8MTc3NTYzMDk1MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1721378466942-4f83ff2a84ef?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5fHx0cmF2ZWwlMjBwYXltZW50fGVufDB8fHx8MTc3NTYzMDk1MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1721378466942-4f83ff2a84ef?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5fHx0cmF2ZWwlMjBwYXltZW50fGVufDB8fHx8MTc3NTYzMDk1MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1721378466942-4f83ff2a84ef?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5fHx0cmF2ZWwlMjBwYXltZW50fGVufDB8fHx8MTc3NTYzMDk1MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="5184" height="3888" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1721378466942-4f83ff2a84ef?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5fHx0cmF2ZWwlMjBwYXltZW50fGVufDB8fHx8MTc3NTYzMDk1MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3888,&quot;width&quot;:5184,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A person holding a wallet with a bank note sticking out of it&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A person holding a wallet with a bank note sticking out of it" title="A person holding a wallet with a bank note sticking out of it" srcset="https://images.unsplash.com/photo-1721378466942-4f83ff2a84ef?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5fHx0cmF2ZWwlMjBwYXltZW50fGVufDB8fHx8MTc3NTYzMDk1MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1721378466942-4f83ff2a84ef?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5fHx0cmF2ZWwlMjBwYXltZW50fGVufDB8fHx8MTc3NTYzMDk1MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1721378466942-4f83ff2a84ef?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5fHx0cmF2ZWwlMjBwYXltZW50fGVufDB8fHx8MTc3NTYzMDk1MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1721378466942-4f83ff2a84ef?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHw5fHx0cmF2ZWwlMjBwYXltZW50fGVufDB8fHx8MTc3NTYzMDk1MHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="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"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@abushihabmarey">Mohamed Marey</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>When someone books a hotel through a travel platform, the transaction looks simple. The traveler pays, the hotel gets paid, everyone moves on. What actually happens underneath is closer to international wire transfer choreography.</p><p>A customer in Germany books a hotel in Vietnam. They pay in euros. The hotel expects Vietnamese dong. Our platform takes a commission in US dollars. That is three currencies moving through a single booking. Multiply that by a thousand bookings a day across twenty countries, and you start to understand why payment infrastructure in travel is a different engineering problem than payment infrastructure in e-commerce.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://henrylyadamo.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>We learned this through an expensive first month in production. Our team builds <a href="https://adamosoft.com/travel-and-hospitality-software-development/">custom travel and hospitality software</a>, and payment infrastructure turned out to be the component we most underestimated. Our initial payment system leaked margin on FX spreads, silently failed 14% of European card transactions, and made month-end reconciliation a two-day manual exercise. This is the story of how we rebuilt it.</p><h2>The problem with &#8220;just use Stripe&#8221;</h2><p>Stripe is excellent. We still use it. But &#8220;integrate Stripe&#8221; is not a payment strategy for travel. It is a starting point that leaves three critical gaps unfilled.</p><p>The first gap is currency timing. A traveler searches for a hotel and sees a price in euros. Ten minutes later, they complete the checkout. The exchange rate has moved. The price they saw is no longer the price the platform receives after conversion. Someone absorbs that difference. If it is the platform, margin erodes quietly across thousands of transactions. If it is the customer, they see a different price at checkout than at search, and trust breaks immediately.</p><p>The second gap is split payouts. A single tour package booking might involve a hotel, a transport provider, and a local guide. Each expects payment in their own currency. Each has different payment terms. Hotels want payment within 48 hours of checkout. Guides might accept weekly batch transfers. The platform needs to hold funds, convert at the right moment, and pay each supplier on their terms while keeping its own books clean.</p><p>The third gap is European authentication. Strong Customer Authentication (SCA) is mandatory in the EEA and UK. If the checkout does not handle 3D Secure properly, European card transactions fail without any visible error. The customer simply sees &#8220;payment declined&#8221; and leaves. We lost 14% of European transactions before we understood this.</p><h2>The decision that saved us the most money</h2><p>We evaluated three approaches to currency conversion. The first was converting everything to USD at payment time. Simple to build, but the customer sees price fluctuations between search and checkout. The second was displaying in local currency, charging in local currency, and settling in USD. Better experience, but the platform absorbs the FX spread on every transaction. The third was locking the exchange rate when the user starts a search session, adding a small margin buffer, and holding that rate through checkout.</p><p>We chose the third approach. When a traveler begins searching, we lock the current exchange rate with a 1.5% buffer and store it for 30 minutes. Every price they see during that session uses the locked rate. When they pay, the rate is the same one they saw at search time. No surprises.</p><p>The 1.5% buffer exists because exchange rates move during those 30 minutes. Without it, roughly 8% of our transactions would have resulted in the platform receiving less than the base price after conversion. The buffer absorbs that movement. It is small enough that prices remain competitive, large enough that the platform does not leak margin.</p><p>Internally, we store all prices in USD and convert at the API layer. This single decision simplified accounting, supplier payouts, and financial reporting more than any other architectural choice we made.</p><h2>Fixing European card declines without adding friction</h2><p>The 14% decline rate on European cards was our most urgent problem. The root cause was straightforward: our checkout redirected to a 3D Secure page and assumed the customer would complete authentication. Many did not. Some were confused by the redirect. Others experienced timeouts. The transaction failed, and our system recorded it as a generic decline.</p><p>The fix was treating 3D Secure as a first-class part of the checkout flow rather than an afterthought. Instead of redirecting the customer away from our page, we used Stripe&#8217;s Payment Intents API to handle authentication inline. The customer completes the 3DS challenge without leaving the checkout. If their bank does not require 3DS, the payment proceeds normally with no extra steps.</p><p>After this change, European card decline rates dropped from 14% to 3.2%. That single fix recovered more revenue than any feature we shipped that quarter.</p><h2>The reconciliation problem nobody warns you about</h2><p>Building the payment flow is the visible work. Reconciliation is the invisible work that determines whether the finance team trusts the system.</p><p>A single booking generates multiple financial events: a customer payment in one currency, a platform fee in another, and one or more supplier payouts in potentially different currencies, each at different exchange rates, on different dates. At month-end, the finance team needs to trace every dollar from customer payment through to supplier payout, account for FX gains and losses, and confirm that the numbers balance.</p><p>Our first system made this nearly impossible. We did not record the exact exchange rate used for each transaction. We did not separate platform fees from supplier payouts in a way that survived currency conversion. Reconciliation took two full days every month, mostly spent in spreadsheets trying to reverse-engineer what happened.</p><p>The rebuilt system stores three things on every transaction: the locked exchange rate at payment time, the actual exchange rate at supplier payout time, and the exact amounts in both the base currency and the local currency. We batch supplier payouts by currency, so all payouts in the same currency use the same rate, which makes reconciliation predictable.</p><p>Monthly reconciliation now takes four hours, most of which is spot-checking automated reports rather than reconstructing data.</p><h2>The numbers after rebuilding</h2><p>After four months with the new payment infrastructure:</p><ul><li><p>European card decline rate dropped from 14% to 3.2%</p></li><li><p>FX margin leakage went from roughly $2,100 per month to $180</p></li><li><p>Reconciliation time went from 2 days of manual work to 4 hours</p></li><li><p>Supplier payout failures dropped from 6% to 0.8%</p></li></ul><h2>What I would tell a CTO building this today</h2><p>If you are building a <a href="https://adamosoft.com/blog/travel-software-development/ai-powered-travel-booking-platform/">travel booking platform</a> that handles international payments, here is what I wish someone had told me before we started.</p><p>First, invest in the reconciliation layer before you invest in the payment flow. Stripe and Adyen handle the actual money movement well. What they do not handle is making sure your finance team can trace every cent across three currencies, two suppliers, and a cancellation that happened three weeks after the original booking. Build that traceability from day one. Retrofitting it is painful and expensive.</p><p>Second, treat 3D Secure as a product decision, not a compliance checkbox. The difference between a redirect-based 3DS flow and an inline one is the difference between losing 14% of European revenue and losing 3%.</p><p>Third, lock your FX rates at the moment the customer starts browsing, not at the moment they pay. The customer&#8217;s experience of price consistency matters more than saving a few basis points on conversion timing. A small margin buffer (1-2%) covers the rate movement and protects the platform without making prices uncompetitive.</p><p>Multi-currency travel payment processing is one of those problems that looks simple from the outside and reveals its complexity only after the first month in production. The payment itself is the easy part. The hard part is making sure the numbers still make sense when the currencies, the suppliers, and the timelines all diverge.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://henrylyadamo.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>