Grab Tech Grab's Engineering team solves critical transportation challenges and makes transport freedom a reality for 620 million people in Southeast Asia. https://engineering.grab.com/ Fri, 13 Mar 2026 07:52:56 +0000 Fri, 13 Mar 2026 07:52:56 +0000 Jekyll v4.4.1 Enabling R8 optimization at scale with AI-assisted debugging <p>Grab is Southeast Asia’s leading superapp, providing a suite of services that bring essential needs to users throughout the region. Its offerings include ride-hailing, food delivery, parcel delivery, mobile payments, and more. With safety, efficiency, and user-centered design at heart, Grab remains dedicated to solving everyday issues and improving the lives of millions. As our app continued to expand, we identified platform-level performance challenges that were affecting user experience across the board. In this article, we share how we successfully enabled R8 optimization for the <a href="https://play.google.com/store/apps/details?id=com.grabtaxi.passenger&amp;hl=en">Grab Android app</a>, achieving significant improvements in app size, startup time, and stability through innovative AI-assisted debugging techniques.</p> <h2 id="introduction">Introduction</h2> <p>Since 2024, our team observed a concerning trend: Application Not Responding (ANR) rates were spiking across the Grab app. Unlike typical isolated issues, the data revealed that ANRs were happening everywhere, not confined to specific features or modules. This pattern pointed to platform-level causes, with our analysis showing strong correlations between ANRs and several factors: memory pressure (particularly when garbage collection was triggered), ad-heavy user flows, complex layouts involving Jetpack Compose embedded within XML layouts, and XML views embedded within Compose code.</p> <p>The Android community had long proven that R8 optimization (beyond basic code shrinking) could deliver substantial performance gains and app size reductions. As Grab has been adopting Jetpack Compose over the last two years, <a href="https://developer.android.com/develop/ui/compose/performance#config">Google’s Jetpack Compose performance documentation</a> specifically recommends R8 optimization for Compose-heavy applications. It became particularly relevant, making it a natural solution for our systemic performance issues.</p> <p>In fact, enabling R8 optimization was not a new idea for our team. It had been identified and flagged as a high-impact solution multiple times over the years, yet each attempt fell short. Here’s why.</p> <h2 id="the-challenge-at-scale">The challenge at scale</h2> <p>Our app operates at scale, with over 9 million lines of code and 100+ engineers working on it daily. While we had basic R8 shrinking enabled, advanced optimization had proven challenging despite multiple attempts over six years (with different tools and approaches over the years).</p> <p>In 2022, we almost made it - successfully rolling out R8 optimization to GEA (our early access build), but unfortunately, we faced <a href="https://issuetracker.google.com/u/0/issues/240077160">critical roadblocks</a> that compelled us to put the project on hold. After analyzing our previous attempts and the current project situation, we identified three fundamental challenges that had to be solved simultaneously.</p> <p>This article details how we overcame each challenge through targeted innovations: <strong>AI-Assisted Debugging</strong> for slow investigation cycles, <strong>Pragmatic Testing Strategy</strong> for validation at scale, and <strong>Optimized Feedback Loop</strong> for rapid iteration.</p> <h2 id="understanding-r8-optimization">Understanding R8 optimization</h2> <p>Before diving into our solution, it’s important to understand what R8 optimization actually provides beyond basic R8 shrinking.</p> <div class="post-image-section"><figure> <img src="/img/r8-optimization/figure-1.png" alt="" style="width:70%" /><figcaption align="middle">Figure 1. The R8 processing pipeline involves multiple interconnected phases that transform, analyze, and optimize code. Understanding this complexity helps explain both the benefits of optimization and why debugging issues become challenging.</figcaption> </figure> </div> <h3 id="what-we-had-in-place">What we had in place</h3> <p>With <code class="language-plaintext highlighter-rouge">minifyEnabled=true</code> and <code class="language-plaintext highlighter-rouge">shrinkResources=true</code> using <code class="language-plaintext highlighter-rouge">proguard-android.txt</code>, we already had:</p> <ul> <li><strong>Tree shaking (shrink phase)</strong>: Removes unused/unreachable code.</li> <li><strong>Code minification (obfuscation)</strong>: Renames classes/methods to short names.</li> <li><strong>Resource shrinking</strong>: Removes unused XML files and drawables.</li> <li><strong>Desugaring</strong>: Java 8+ compatibility.</li> </ul> <h3 id="whats-new-with-optimization">What’s new with optimization</h3> <p>By switching to <code class="language-plaintext highlighter-rouge">proguard-android-optimize.txt</code>, we gained access to:</p> <ul> <li><strong>Method inlining</strong>: Replaces method calls with actual code, reducing call overhead.</li> <li><strong>Class merging</strong>: Makes code more compact by combining similar classes.</li> <li><strong>Constant folding</strong>: Pre-computes constant expressions at compile time.</li> <li><strong>Dead code elimination</strong>: More aggressive than tree shaking, removes unreachable branches.</li> <li><strong>Devirtualization</strong>: Converts virtual calls to direct calls when possible.</li> </ul> <p>These optimizations work together to improve runtime performance while significantly reducing app size.</p> <h2 id="three-core-challenges">Three core challenges</h2> <p>With context on R8 optimization benefits, why is enabling it so difficult at Grab’s scale? After analyzing our previous failed attempts and the current project situation, we identified three fundamental challenges that had to be solved simultaneously.</p> <h3 id="challenge-1-slow-debugging">Challenge 1: Slow debugging</h3> <p>R8 optimization issues are notoriously difficult to debug:</p> <ul> <li>Code is <strong>obfuscated</strong>, class names become <code class="language-plaintext highlighter-rouge">a</code>, <code class="language-plaintext highlighter-rouge">b</code>, <code class="language-plaintext highlighter-rouge">c</code>.</li> <li>Code is <strong>modified</strong>, inlined, merged, and optimized beyond recognition.</li> <li>Stack traces are <strong>unreadable</strong> without proper mapping files when crashes occur.</li> <li><strong>Pinpointing the root cause</strong> requires manual reverse engineering.</li> </ul> <p>Our limited resources compound the challenge: with only one engineer leading the project, most issues had to be either addressed directly or have solutions provided for other teams to fix. Manual decompilation, deobfuscation, and context gathering for each issue are inherently time-consuming, making the investigation cycle slow.</p> <h3 id="challenge-2-testing-at-scale">Challenge 2: Testing at scale</h3> <p>R8 optimization affects every corner of the app. Unlike feature-specific changes, enabling optimization transforms how the entire codebase is compiled, inlined, and optimized. A single misconfiguration or missing keep rule can break seemingly unrelated features across different modules and libraries.</p> <p>When we first enabled R8 optimization, the impact was immediate and widespread: most of the app’s features simply stopped working correctly. This presented us with a deeper problem, not just how to test, but what kind of testing strategy would actually give us confidence to roll out to production.</p> <p>In theory, R8 optimization works reliably with standard codebases that follow Google’s and the community’s best practices. However, the Grab app is a ~10-year-old project at a massive scale. Legacy code patterns, reflection usage, and SDK integrations accumulated over a decade create numerous edge cases.</p> <p>This combination makes comprehensive testing necessary, but at our scale, it’s nearly impossible to execute due to:</p> <ul> <li><strong>Full regression testing</strong> would require significant effort from all teams across the organization.</li> <li><strong>Quality Assurance (QA) resource constraints</strong> make exhaustive testing impractical.</li> <li><strong>High-quality bar</strong>: At Grab, app stability and zero runtime errors are non-negotiable standards.</li> </ul> <p>This creates a paradox: we need comprehensive testing precisely because we can’t guarantee standards everywhere, yet the scale makes such testing infeasible.</p> <h3 id="challenge-3-slow-feedback">Challenge 3: Slow feedback</h3> <p>Due to the large scale of the project, compiling a build with R8 optimization enabled on a standard engineering laptop is physically impossible. This created a significant bottleneck: a slow feedback loop where every experimental change required a remote CI build to verify, with each R8-optimized build taking up to 2 hours to complete.</p> <p>Additionally, R8 treats debug and release build types differently. At Grab, we have a QA build for QA testing. This is a debug build type with R8 enabled, pointed to our staging environment. We had to ensure this QA build’s R8 configuration matched our production build exactly. This alignment was critical for catching R8-specific issues during QA testing that would actually reflect production behavior</p> <h2 id="our-three-innovation-solution">Our three-innovation solution</h2> <p>To overcome these three fundamental challenges, we developed a comprehensive strategy centered on targeted innovations that addressed each bottleneck.</p> <h3 id="innovation-1-ai-assisted-debugging">Innovation 1: AI-assisted debugging</h3> <p><strong>Solving challenge 1</strong>:</p> <p>How do we speed up the investigation of R8 issues in obfuscated, optimized code at scale? The answer is in emerging AI technology that wasn’t available during our previous attempts.</p> <p><strong>The AI context at Grab</strong>:</p> <p>Unlike 2022 and earlier attempts, the landscape had changed dramatically. After the LLM explosion, Grab proactively promoted AI (LLM) usage to boost engineering productivity. Over the past two years, Grab has dedicated 1-2 months annually for engineers to learn how to use AI efficiently. This investment in AI literacy became crucial for this project.</p> <p>This year (2025), my team gained experience building MCP (Model Context Protocol) servers and identified an opportunity: applying this technology to solve the R8 debugging challenge.</p> <p><strong>Our solution</strong>:</p> <p>At Grab, we use <strong>GitLab for Continuous Integration and Continuous Delivery (CI/CD)</strong>. To tackle R8 debugging bottlenecks, we built a comprehensive solution combining:</p> <ul> <li><a href="#build-mcp-tools-eliminate-manual-reverse-engineering">Custom MCP tools</a>.</li> <li><a href="#ai-and-ci-pipeline-workflow">AI-assisted GitLab CI integration</a>.</li> </ul> <h4 id="build-mcp-tools-eliminate-manual-reverse-engineering">Build MCP tools: Eliminate manual reverse engineering</h4> <ul> <li><strong>Automatic Android Application Package (APK) decompilation</strong>: Parse and decompile APKs.</li> <li><strong>Stack trace deobfuscation</strong>: Automatically map obfuscated traces to source code.</li> <li><strong>Class/method context fetching</strong>: Pull relevant decompiled code sections for analysis.</li> </ul> <h4 id="ai-and-ci-pipeline-workflow">AI and CI pipeline workflow:</h4> <p>We developed a systematic two-phase approach for investigating and fixing each runtime issue, combining AI assistance with parallel testing:</p> <p><strong>Phase 1: MCP server tools for debugging</strong></p> <ol> <li><strong>Detect runtime issue</strong>: From End-to-End (E2E) tests, QA testing, or crash reports.</li> <li><strong>MCP tool orchestrates APK analysis</strong>: Coordinates decompilation tools for reverse engineering.</li> <li><strong>MCP tool provides decompiled code context</strong>: Pulls and decompiles problematic code sections.</li> <li><strong>Engineer and AI analysis</strong>: The engineer uses AI assistance to analyze the decompiled code context and note down multiple solution approaches.</li> </ol> <p><strong>Phase 2: GitLab CI integration</strong></p> <p>We leveraged the GitLab CLI tool (<a href="https://docs.gitlab.com/cli/"><code class="language-plaintext highlighter-rouge">glab</code></a>) and instructed AI to use it for interacting with our CI pipeline:</p> <ol> <li><strong>AI creates multiple Merge Requests (MRs)</strong>: Using <code class="language-plaintext highlighter-rouge">glab</code> CLI, AI creates merge requests for different solution approaches from Phase 1, each triggering CI compilation.</li> <li><strong>Track progress</strong>: Maintain an MD file as the source of truth for the investigation, containing all notes about the issue (root cause analysis, test cases, test branches, CI build status).</li> <li><strong>AI fetches APK from CI</strong>: Using <code class="language-plaintext highlighter-rouge">glab</code> CLI to retrieve built APKs from completed CI pipelines.</li> <li><strong>Verify</strong>: Ask AI to use ADB install APK, then manually test the fix.</li> <li><strong>Iterate</strong>: If issues remain, loop back to step 2 for further analysis.</li> </ol> <p><strong>Why this worked</strong>:</p> <p>Our approach functions as an AI assistant that:</p> <ul> <li><strong>Decodes the obfuscated code</strong> automatically.</li> <li><strong>Finds the relevant code sections</strong> without manual searching.</li> <li><strong>Suggests multiple solutions</strong> based on the context provided by the MCP tools.</li> <li><strong>Creates multiple test branches simultaneously</strong> and runs parallel CI builds to test different approaches.</li> <li><strong>Tracks everything</strong> to ensure no progress is lost on complex investigations.</li> </ul> <p>Instead of testing solutions one-by-one (waiting 2 hours per build), AI creates multiple MRs in parallel, dramatically accelerating the verification process. Engineers focus on making decisions about which solutions to pursue while the AI handles both the mechanical work and the parallel experimentation.</p> <p><strong>The impact: Accelerating investigation</strong></p> <p>While investigating a single R8 issue might still take time, our MCP tools dramatically accelerated critical investigation tasks. Manual tasks that previously took hours (decompilation, deobfuscation, context gathering) were reduced to minutes. Additionally, AI assistance significantly sped up the analysis phase, helping engineers quickly identify patterns, suggest solutions, and explore multiple approaches in parallel, both analytically and through simultaneous CI builds, further accelerating the overall investigation process.</p> <h3 id="innovation-2-pragmatic-testing-strategy">Innovation 2: Pragmatic testing strategy</h3> <p><strong>Solving challenge 2</strong>:</p> <p>How do we do testing at scale? How do we validate R8 optimization across a mature codebase containing more than nine million lines of code when comprehensive testing is necessary but impossible? Our solution came from a critical insight about R8 issues at scale.</p> <p><strong>Key insight</strong>:</p> <p>From our experience, R8 issues tend to share similar root causes across the codebase. Legacy patterns like reflection usage, parser implementations, and dynamic class loading follow consistent patterns within a large codebase. This insight led to two key advantages:</p> <ul> <li><strong>Fix one, help many</strong>: Fixing one place often resolves issues in others.</li> <li><strong>Pattern recognition</strong>: Once we identify a pattern, we can search the codebase to find similar issues instead of waiting for QA to discover them.</li> </ul> <p>If we could identify and fix these pattern-based issues, we could address many problems without testing every corner of the app. We decided to start with critical paths and expand from there. This “ripple effect” strategy began at the center with the most important flows, then expanded by identifying common root causes and similar patterns across the codebase.</p> <p>With this foundation, we designed a validation pipeline that progressively increased confidence:</p> <p><strong>Progressive, Risk-Based validation strategy</strong></p> <ul> <li> <p><strong>Stage 1: E2E tests - pattern discovery phase</strong>: Fortunately, we had existing E2E tests covering most critical paths in the project, and they could be executed with R8 optimization enabled. Initially, all E2E tests failed after enabling optimization. This became our opportunity for pattern discovery: we systematically fixed issues and applied our pattern-based approach to resolve similar problems across the project.</p> </li> <li> <p><strong>Stage 2: QA smoke tests - coverage expansion</strong>: After E2E tests stabilized, we requested our QA team to run smoke tests on critical flows, especially those not covered by E2E automation. This caught additional edge cases and validated that the pattern-based fixes we applied were effective across different user journeys. We fixed any issues that appeared during this phase.</p> </li> <li> <p><strong>Stage 3: Daily QA build enablement - real-world integration</strong>: After confirming stability in controlled testing, we made a significant decision: enable R8 optimization in our daily QA build (the build our QA team uses for daily feature testing). This integrated R8 optimization into the normal development workflow without requiring additional testing effort.</p> </li> <li> <p><strong>Stage 4: Regression testing and Grab Early Access (GEA) - parallel production-scale validation</strong>: After confirming stability in daily QA builds, we moved to production-scale validation with two parallel tracks. Every release at Grab includes <strong>regression testing</strong> covering all critical paths and new features. With R8 optimization now enabled in the QA build, we ran regression tests using this build for a few weeks, providing sustained validation across multiple release cycles. One week after regression testing, we rolled out to <strong>GEA</strong>, Grab’s internal production release channel for Grab employees and partners. While GEA users typically receive features one week before general production rollout, for this R8 optimization project, we extended the GEA phase to 2 weeks, given the significance of the change. With hundreds of daily active users using the app in real-world production conditions during this extended period, we encountered only one remaining R8 issue during the GEA phase. This combination of regression testing and real-world GEA production usage gave us the confidence needed before full production rollout.</p> </li> </ul> <p><strong>Testing Approaches That Don’t Work with R8</strong>:</p> <ul> <li><strong>Unit tests</strong>: Run on Java JVM, while R8 optimizations affect Android Runtime behavior - fundamentally different environments</li> <li><strong>UI tests with R8</strong>: Community solutions exist as Gradle plugins, but <a href="https://engineering.grab.com/how-grab-is-blazing-through-the-super-app-bazel-migration">our tests run on Bazel</a> - complex setup and reliability concerns</li> </ul> <p><strong>Pattern-based issue resolution</strong>:</p> <p>Throughout these validation phases, when we identified R8 issues, we followed a systematic pattern-based resolution process.</p> <ol> <li><strong>Identify the issue</strong>: Catch the failure through E2E, QA, or monitoring.</li> <li><strong>Find the pattern</strong>: Analyze the root cause to identify if it’s a common pattern across the codebase.</li> <li><strong>Detect similar instances</strong>: Search the entire codebase to find the same pattern across different modules and the internal SDKs.</li> <li><strong>Coordinate fixes</strong>: Create tickets requesting teams to modify their code to prevent the same issue in their modules.</li> </ol> <p>This approach required cross team coordination for fixing, but critically, not for testing. The difference is significant: asking teams to fix identified issues in their modules is much more scalable than requiring all teams to perform comprehensive testing upfront.</p> <p><strong>Production rollout results</strong>:</p> <p>When we made it to production, only one issue escaped to production. Notably, we had actually detected this issue through our pattern-based approach during testing and created a ticket for the responsible team to fix it. However, with ongoing daily development, the team missed one instance when implementing the fix, which caused the production issue.</p> <p>This demonstrates that while our testing strategy worked effectively, human coordination challenges can still occur at scale. With a project of this scale, having only one small production issue is considered a highly successful rollout.</p> <p>This approach transformed an “impossible” comprehensive testing problem into a manageable, systematic validation process, reducing what would have been months of coordinated testing effort to days, proving that a smart strategy can overcome resource constraints.</p> <h3 id="innovation-3-optimized-feedback-loop">Innovation 3: Optimized feedback loop</h3> <p><strong>Solving challenge 3</strong>:</p> <p>The 2-hour CI builds, and the QA configuration misalignment created a bottleneck for R8 debugging. We addressed this through a comprehensive infrastructure strategy targeting three critical areas:</p> <p><strong>Remote compilation to enable local build and fast feedback loop</strong>:</p> <p>At Grab, we used to use <a href="https://github.com/buildfoundation/mainframer">Mainframer</a> for remote execution to handle slow performance on local Gradle builds. However, since <a href="https://engineering.grab.com/how-grab-is-blazing-through-the-super-app-bazel-migration">migrating to Bazel</a> (only for the debug build without R8 enabled), we removed the large-scale Mainframer setup for every engineer. From that experience, to tackle the local compilation blocker for R8 builds, we decided to deploy a new Mainframer setup, a much smaller one with one powerful EC2 instance, serving as a solution for local compilation in a short time.</p> <p>This targeted deployment transformed physically impossible local R8 builds into a manageable remote process, enabling engineers to test R8 changes without requiring powerful local hardware.</p> <p>The performance improvement was substantial: <strong>from up to 2 hours in CI to around 1 hour with Mainframer</strong> - a ~50% reduction that enabled rapid iteration cycles essential for R8 debugging.</p> <p><strong>QA build configuration alignment</strong>:</p> <p>We eliminated the critical gap between QA and production R8 behavior by aligning build configurations exactly. The key change was setting <code class="language-plaintext highlighter-rouge">debuggable = false</code> for QA builds while maintaining the environment configuration:</p> <div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">buildTypes</span> <span class="o">{</span> <span class="n">debug</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">isQaBuild</span><span class="o">())</span> <span class="o">{</span> <span class="n">minifyEnabled</span> <span class="kc">true</span> <span class="n">shrinkResources</span> <span class="kc">true</span> <span class="n">debuggable</span> <span class="kc">false</span> <span class="n">buildConfigField</span> <span class="s1">'boolean'</span><span class="o">,</span> <span class="s1">'DEBUG'</span><span class="o">,</span> <span class="s1">'true'</span> <span class="o">...</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p>Since, from our understanding, R8 applies different optimization levels based on the <code class="language-plaintext highlighter-rouge">debuggable</code> flag, with more aggressive optimizations when debuggable=false, this ensured our QA testing reflected actual production R8 processing. We preserved <code class="language-plaintext highlighter-rouge">DEBUG = true</code> to maintain staging environment routing while achieving R8 parity.</p> <p>This infrastructure foundation was essential, providing faster feedback loops that accelerated verification and investigation, while the QA build configuration matching production exactly was critical for catching real production issues during testing.</p> <h2 id="a-lucky-break">A lucky break</h2> <p>Perhaps most surprising: the R8 flakiness issue that blocked us in 2022 (<a href="https://issuetracker.google.com/u/0/issues/240077160">Issue #240077160</a>) appears to have been resolved by the R8 team. We encountered no build determinism issues during this attempt, which significantly smoothed our path to production.</p> <h2 id="results">Results</h2> <p>After ~10 weeks of systematic implementation <strong>led by one engineer</strong> collaborating with multiple teams across the organization, we achieved substantial improvements using <strong>Android Gradle Plugin 8.6.X</strong>:</p> <ul> <li><strong>Stability</strong>: Around 25% reduction in ANR rates.</li> <li><strong>App size:</strong> A 16% decrease in download size on our reference device (zipped APK).</li> <li><strong>Performance:</strong> Nearly 27% improvement in startup time. <em>An interesting discovery: After enabling R8 optimization, we saw ~12% app startup improvement. However, during our analysis, we discovered that our existing Baseline and Startup Profiles implementation was incorrect. We reimplemented it properly, and the combination of R8 optimization plus the corrected profiles delivered the full 27% improvement.</em></li> </ul> <p>These results exceeded our initial targets and validated the significant effort required to enable R8 optimization at scale.</p> <h2 id="whats-next">What’s next</h2> <p>Our journey doesn’t end here. We’re exploring several areas for continued optimization:</p> <ul> <li><strong>R8 full mode</strong>: More extreme/aggressive optimization than the current mode for additional performance benefits.</li> <li><strong>Revisit R8 keep rules</strong>: Clean up unnecessary rules that prevent optimization, and implement a governance solution to guardrail R8 rules in our pre-merge CI pipeline.</li> </ul> <h2 id="conclusion">Conclusion</h2> <p>Enabling R8 optimization for the Grab Android app at scale required innovation beyond traditional debugging approaches. By combining AI-assisted debugging, pragmatic testing strategies, and infrastructure investment, we overcame challenges that had blocked previous attempts for many years.</p> <p>For other teams considering R8 optimization at scale: the journey is challenging, but the results speak for themselves. With the right tools, strategy, and team collaboration, it’s achievable even for the largest codebases.</p> <h2 id="join-us">Join us</h2> <p>Grab is a leading superapp in Southeast Asia, operating across the deliveries, mobility, and digital financial services sectors. Serving over 800 cities in eight Southeast Asian countries: Cambodia, Indonesia, Malaysia, Myanmar, the Philippines, Singapore, Thailand, and Vietnam. Grab enables millions of people every day to order food or groceries, send packages, hail a ride or taxi, pay for online purchases, or access services such as lending and insurance, all through a single app. We operate supermarkets in Malaysia under Jaya Grocer and Everrise, which enables us to bring the convenience of on-demand grocery delivery to more consumers in the country. As part of our financial services offerings, we also provide digital banking services through GXS Bank in Singapore and GXBank in Malaysia. Grab was founded in 2012 with the mission to drive Southeast Asia forward by creating economic empowerment for everyone. Grab strives to serve a triple bottom line. We aim to simultaneously deliver financial performance for our shareholders and have a positive social impact, which includes economic empowerment for millions of people in the region, while mitigating our environmental footprint.</p> <p>Powered by technology and driven by heart, our mission is to drive Southeast Asia forward by creating economic empowerment for everyone. If this mission speaks to you, <a href="https://grab.careers/">join our team today</a>!</p> Thu, 12 Mar 2026 00:23:00 +0000 https://engineering.grab.com/r8-optimization-at-scale-with-ai-assisted-debugging https://engineering.grab.com/r8-optimization-at-scale-with-ai-assisted-debugging AI Engineering Reclaiming Terabytes: Optimizing Android image caching with TLRU <h2 id="introduction">Introduction</h2> <p>In a previous post, we discussed <a href="https://engineering.grab.com/project-bonsai">Project Bonsai</a>, our initiative to reduce the Grab app’s download size. We successfully reduced the Android Application Package (APK) download size by 26%. This reduction offers a substantial advantage: it minimizes download friction, allowing users to download the app, even on slower networks. However, the battle for storage doesn’t end after installation.</p> <p>The Grab app includes a wide range of features and workflows that heavily depend on image content, particularly in services like transportation and e-commerce. Although some images are packaged within the app binary, a large majority are downloaded from Grab’s server at runtime. To optimize the app’s performance and minimize server expenses, the downloaded images are cached in the app’s storage. This reduces both load times and traffic to Grab’s image server, resulting in better user experience and lower costs. Although we use Least Recently Used (LRU) cache to manage storage, many images can remain in the app storage for extended periods, even if they are no longer relevant.</p> <p>This blog details how we addressed this challenge in our Grab Android app by evolving our standard LRU cache into a <strong>Time-Aware Least Recently Used (TLRU)</strong> cache. This evolution allows us to reclaim storage space without compromising user experience or increasing server costs.</p> <h2 id="understanding-lru-cache-limitations">Understanding LRU cache limitations</h2> <p><em>Note: In this article, when “cache” or “image cache” is mentioned, it specifically refers to <strong>disk cache</strong>, which is the persistent storage on the device’s file system, rather than in-memory cache.</em></p> <p>The Grab Android app uses the <a href="https://github.com/bumptech/glide">Glide library</a> as its primary image loading framework. Glide provides excellent features for efficient image loading, caching, and display. At its core, by default, Glide uses a cloned version of <a href="https://github.com/bumptech/glide/blob/master/third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/DiskLruCache.java">Jake Wharton’s DiskLruCache</a> for disk-based caching.</p> <p>To prevent unlimited cache growth, we configured the LRU cache with a maximum size limit of 100 MB. However, our analytics revealed that the 90th percentile (P90) of users were consistently reaching this 100 MB limit, meaning the cache was constantly at capacity. Conversely, for users whose cache hadn’t yet reached the 100 MB threshold, images were never removed, even if they were outdated by several months and no longer relevant.</p> <p>Our analysis revealed that image caching was a major contributor to the app’s disk footprint, and without proactive management, this would only worsen as we continued adding features and content to Grab’s superapp.</p> <h3 id="how-disklrucache-works">How DiskLruCache works</h3> <p>The LRU cache algorithm manages storage by maintaining entries in access order and automatically evicting the oldest unused entries when space is needed.</p> <p><strong>Figure 1 and 2</strong> illustrates how LRU cache trimming works. These diagrams present an LRU cache with a maximum size of 100 MB containing three cache entries totaling 95 MB. When a new 25 MB cache entry is added, it exceeds the cache’s maximum size.</p> <div class="post-image-section"><figure> <img src="/img/image-caching/figure-1.png" alt="" style="width:60%" /><figcaption align="middle">Figure 1. A new cache entry is added to an LRU cache that's near its 100 MB capacity, exceeding the limit.</figcaption> </figure> </div> <div class="post-image-section"><figure> <img src="/img/image-caching/figure-2.png" alt="" style="width:60%" /><figcaption align="middle">Figure 2. The LRU cache automatically trims the least recently used entry to bring the total size back within the 100 MB limit.</figcaption> </figure> </div> <h2 id="the-challenge">The challenge</h2> <p>While DiskLruCache efficiently manages cache size, it has a critical limitation: It does not account for the age of cached content. Due to the lack of time-based eviction rules, the cache does not remove outdated entries until it exceeds the maximum size. This meant that stale promotional images, images from infrequently used features, and outdated content continued occupying disk space indefinitely, as long as the cache remained under the size limit.</p> <p>What we needed was a cache mechanism that could:</p> <ul> <li> <p><strong>Maintain LRU cache benefits</strong>: Preserve efficient caching for users who actively use the app features.</p> </li> <li> <p><strong>Remove stale content based on time:</strong> Automatically identify and evict outdated entries, not just rely on storage constraints.</p> </li> <li> <p><strong>Protect user experience</strong>: Ensure images still load quickly without cache misses.</p> </li> <li> <p><strong>Keep server costs low</strong>: Avoid increased server requests from premature cache evictions.</p> </li> </ul> <p>These requirements pointed us toward an enhanced LRU approach. We needed to enhance LRU with time awareness while preserving its proven size-management capabilities.</p> <h2 id="tlru-cache-the-solution">TLRU cache: The solution</h2> <p>To address these limitations, we developed a new LRU cache variant named TLRU that extends traditional LRU by introducing time-based eviction while maintaining size-based cache management.</p> <h3 id="core-tlru-attributes">Core TLRU attributes</h3> <p>TLRU introduces three core attributes to manage cache entries:</p> <ul> <li> <p><strong>Time-To-Live (TTL)</strong>: A threshold that determines when a cache entry is considered expired. An entry is expired if <code class="language-plaintext highlighter-rouge">(current_time - last_accessed) &gt; TTL</code>. Expired entries are automatically removed during cache operations.</p> </li> <li> <p><strong>Minimum cache size threshold</strong>: A safety net that ensures a baseline set of essential images always remains cached, even when entries expire. This prevents complete cache deletion when users haven’t used the app for more than the TTL period, maintaining app responsiveness for returning users instead of starting with an empty cache.</p> </li> <li> <p><strong>Maximum cache size</strong>: Inherited from LRU cache, this enforces the upper storage limit (100 MB in our case). When exceeded, the least recently used entries are evicted regardless of their age.</p> </li> </ul> <p>Together, these attributes ensure TLRU maintains optimal cache size by managing both storage constraints and temporal relevance, reducing app disk footprint without impacting user experience.</p> <h3 id="tlru-cache-trimming-in-action">TLRU cache trimming in action</h3> <p>To better understand how TLRU works in practice, let’s walk through a comprehensive example. The following diagrams demonstrate how the TLRU cache evaluates and trims entries based on both time and size constraints.</p> <p>Our TLRU cache configuration includes:</p> <ul> <li> <p><strong>Maximum cache size</strong>: 100 MB - the storage limit that triggers size-based eviction.</p> </li> <li> <p><strong>Minimum size threshold</strong>: 20 MB - the safety net that protects essential cached content.</p> </li> <li> <p><strong>TTL</strong>: 20 days - entries older than this are considered expired.</p> </li> </ul> <p>Each cache entry includes <code class="language-plaintext highlighter-rouge">last_accessed</code> metadata containing the timestamp of its most recent access. When an entry is first created, this timestamp is initialized with the creation time. This timestamp determines whether an entry has expired based on the formula:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Entry is expired if: (current_time - last_accessed) &gt; TTL </code></pre></div></div> <p>For this walkthrough, we’ll use <code class="language-plaintext highlighter-rouge">current_time = Day 100</code> as our starting point.</p> <h4 id="initial-cache-state-analysis">Initial cache state analysis</h4> <p>Our example begins with three existing cache entries totaling 95 MB, approaching the 100 MB limit:</p> <ul> <li><strong>Item 1</strong> (8 MB, last accessed Day 82): At 18 days old</li> <li><strong>Item 2</strong> (30 MB, last accessed Day 81): At 19 days old</li> <li><strong>Item 3</strong> (57 MB, last accessed Day 80): At exactly 20 days old, valid at the TTL threshold</li> </ul> <p>When a new 10 MB item is added on Day 100, the cache grows to 105 MB, exceeding our 100 MB limit and triggering size-based eviction.</p> <div class="post-image-section"><figure> <img src="/img/image-caching/figure-3.png" alt="" style="width:60%" /><figcaption align="middle">Figure 3. Initial TLRU cache state and the impact of adding new entries.</figcaption> </figure> </div> <h4 id="size-based-eviction-process">Size-based eviction process</h4> <p>When the cache exceeds its 100 MB limit, TLRU applies traditional LRU eviction logic. <strong>Item 3</strong> is selected for eviction because:</p> <ul> <li> <p>It is the least recently used entry (oldest access time).</p> </li> <li> <p>This demonstrates TLRU maintaining LRU behavior for size enforcement, regardless of expiration status.</p> </li> </ul> <div class="post-image-section"><figure> <img src="/img/image-caching/figure-4.png" alt="" style="width:60%" /><figcaption align="middle">Figure 4. Size-based eviction removes the least recently used entry to enforce storage limits.</figcaption> </figure> </div> <h4 id="time-based-eviction-process">Time-based eviction process</h4> <p>Five days later (Day 105), Item 1 and Item 2 cross the expiration threshold:</p> <p>Despite operating well below the size limit (48 MB &lt; 100 MB), TLRU evaluates expired entries for time-based eviction. Item 2 is removed because it’s expired, and the cache remains above the minimum threshold. Item 1, although also expired, is protected by the minimum threshold rule; removing it would leave only 10 MB, which falls below the 20 MB minimum.</p> <div class="post-image-section"><figure> <img src="/img/image-caching/figure-5.png" alt="" style="width:60%" /><figcaption align="middle">Figure 5. Time-based eviction and minimum threshold protection working together.</figcaption> </figure> </div> <h4 id="tlru-behavior-summary">TLRU behavior summary</h4> <p>This comprehensive example demonstrates TLRU’s three core mechanisms:</p> <ul> <li> <p><strong>Size-based eviction</strong>: Enforces storage limits using traditional LRU ordering (Item 3 removed despite being valid).</p> </li> <li> <p><strong>Time-based eviction</strong>: Proactively removes expired content when safe to do so (Item 2 removed for age).</p> </li> <li> <p><strong>Minimum threshold protection</strong>: Preserves essential cache functionality even with expired content (Item 1 protected despite expiration).</p> </li> </ul> <h2 id="technical-implementation">Technical implementation</h2> <p>Rather than building an image cache from scratch, we recognized that Glide’s bundled <a href="https://github.com/bumptech/glide/blob/master/third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/DiskLruCache.java">DiskLruCache</a> (originally from <a href="https://github.com/JakeWharton/DiskLruCache">Jake Wharton’s implementation</a>) already provided a mature, battle-tested foundation. This implementation is widely adopted across the Android ecosystem and handles complex edge cases like crash recovery, thread safety, and performance optimization that would require substantial effort to replicate.</p> <p>Our approach was pragmatic, we cloned Glide’s DiskLruCache and extended it to support time-based expiration. This strategy allowed us to inherit the existing reliability while adding the temporal awareness we needed for TLRU.</p> <p>To understand our implementation, we’ll first explore how the original DiskLruCache works, then dive into the specific modifications we made to transform it into TLRU.</p> <h3 id="understanding-disklrucache">Understanding DiskLruCache</h3> <p><a href="https://github.com/bumptech/glide/blob/master/third_party/disklrucache/src/main/java/com/bumptech/glide/disklrucache/DiskLruCache.java">DiskLruCache</a> provides a simple cache solution that stores key-value pairs on disk, while also keeping track of their usage to evict the least recently used items when the cache reaches its maximum size. Here is an overview of how DiskLruCache is implemented:</p> <ul> <li> <p><strong>Data storage</strong>: DiskLruCache stores its data in a specified directory, creating files for each entry.</p> </li> <li> <p><strong>Key-based access</strong>: Each entry has a unique key (typically a hash generated by the image loader) used to create the filename of the cached entry.</p> </li> <li> <p><strong>Atomic writes</strong>: When adding an entry, it creates a temporary file and writes the data to it. If successful, it atomically renames the temporary file to the final filename.</p> </li> <li> <p><strong>Cache retrieval</strong>: When reading from the cache, it looks up the key, opens the corresponding file on disk, and returns an InputStream to read the data.</p> </li> <li> <p><strong>Size management</strong>: It maintains a maximum cache size limit. When exceeded, it removes the least recently used items until it is within the specified limit.</p> </li> </ul> <p>The central component that enables this functionality is the journaling mechanism, detailed in the following section.</p> <h4 id="the-journaling-mechanism">The journaling mechanism</h4> <p>The journaling mechanism in DiskLruCache is designed to maintain consistency and prevent data corruption in the cache. The journal file records all cache operations, such as adding, updating, or removing entries. The journaling mechanism is essential in rebuilding the cache metadata during initialization and performing journal compaction to clean up the journal file.</p> <div class="post-image-section"><figure> <img src="/img/image-caching/figure-6.png" alt="" style="width:70%" /><figcaption align="middle">Figure 6. Example of the journaling mechanism in DiskLruCache.</figcaption> </figure> </div> <p><strong>Journal file format</strong>:</p> <p>The journal file is a plain text file that records cache operations line by line.</p> <ul> <li> <p>DIRTY: Indicates the start of a write operation to a cache entry.</p> </li> <li> <p>CLEAN: Indicates that a cache entry was successfully written and closed.</p> </li> <li> <p>REMOVE: Indicates that a cache entry was removed from the cache.</p> </li> <li> <p>READ: Indicates that a cache entry was read.</p> </li> </ul> <p>To gain a comprehensive understanding of the journal file format, refer to the following detailed explanation.</p> <ul> <li> <p><strong>Key information</strong>: Each line includes the key and other relevant information, such as the lengths of the cache entry files.</p> </li> <li> <p><strong>Cache initialization</strong>: Upon initialization, DiskLruCache reads the journal file to reconstruct cache metadata in memory, determining file associations, lengths, and access order. If the journal file is corrupted or missing, the cache will be considered invalid, and DiskLruCache will remove all cache files and start fresh.</p> </li> <li> <p><strong>Cache operations and journal updates</strong>: When performing cache operations like adding, updating, or removing entries, DiskLruCache appends corresponding lines to the journal file, recording the operation details. For example, when starting to write a new cache entry, it writes a <code class="language-plaintext highlighter-rouge">DIRTY</code> line with the key, and when the write is successful, it appends a <code class="language-plaintext highlighter-rouge">CLEAN</code> line with the key and lengths.</p> </li> <li> <p><strong>Synchronization and consistency:</strong> DiskLruCache uses synchronization to ensure that only one thread can access the cache at a time, preventing race conditions and data corruption. It also uses a journalWriter (<code class="language-plaintext highlighter-rouge">java.io.Writer</code>) instance to append operations to the journal file, ensuring that the file is always in a consistent state.</p> </li> <li> <p><strong>Journal compaction</strong>: Over time, the journal file may grow with redundant operations. DiskLruCache periodically compacts the journal by creating a new file that contains only the current cache metadata, then atomically replaces the old file. The compaction process usually happens when the journal file size exceeds a certain threshold.</p> </li> </ul> <p>DiskLruCache ensures consistency and prevents data corruption by using this journaling mechanism, making it a reliable solution for disk-based caching.</p> <h3 id="modifying-disklrucache-for-tlru">Modifying DiskLruCache for TLRU</h3> <p>With a solid understanding of DiskLruCache’s architecture, we can now explore how we extended it to implement the TLRU cache attributes defined earlier.</p> <p>Three primary modifications to DiskLruCache:</p> <ul> <li><a href="#tracking-last-access-time">Tracking last access time</a></li> <li><a href="#time-based-eviction-logic">Time-based eviction logic</a></li> <li><a href="#backward-compatible-migration">Backward-compatible migration</a></li> </ul> <h4 id="tracking-last-access-time">Tracking last access time</h4> <p>To support time-based eviction, the cache needs to track when each entry was last accessed. This information m ust persist across app restarts, so it’s stored in the journal file itself.</p> <p>Modified journal format:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>READ [Cache-Key] [Access-Timestamp] CLEAN [Cache-Key] [File-Size]-[Access-Timestamp] </code></pre></div></div> <p>The timestamps are added to <code class="language-plaintext highlighter-rouge">READ</code> and <code class="language-plaintext highlighter-rouge">CLEAN</code> operations:</p> <ul> <li> <p><code class="language-plaintext highlighter-rouge">READ</code> entries record when a cache entry is accessed, updating its last-access time.</p> </li> <li> <p><code class="language-plaintext highlighter-rouge">CLEAN</code> entries record the creation time when a new entry is successfully added to the cache.</p> </li> </ul> <div class="post-image-section"><figure> <img src="/img/image-caching/figure-7.png" alt="" style="width:70%" /><figcaption align="middle">Figure 7. Example of a TLRU journal file.</figcaption> </figure> </div> <h4 id="time-based-eviction-logic">Time-based eviction logic</h4> <p>The TLRU cache leverages the existing LRU ordering to optimize expiration checking. For each cache operation, it checks if the least recently accessed entry has expired before proceeding with time-based trimming.</p> <p>The diagram below shows how the TLRU cache makes the decision to remove the cache entries.</p> <div class="post-image-section"><figure> <img src="/img/image-caching/figure-8.png" alt="" style="width:70%" /><figcaption align="middle">Figure 8. TLRU eviction decision flow - evaluating cache entries based on time expiration and size constraints.</figcaption> </figure> </div> <p>The algorithm leverages the sorted nature of the cache: if the least recently accessed entry hasn’t expired, no other entries need checking. If it has expired, the cache trim operation walks through entries from oldest to newest, removing all expired ones.</p> <h4 id="backward-compatible-migration">Backward-compatible migration</h4> <p>With an extensive user base, invalidating existing cached images would cause millions of users to experience poor performance while creating massive server traffic spikes and infrastructure costs.</p> <p>One of the challenges was retrieving last-access timestamps from existing LRU entries, as file system APIs do not offer reliable access time data. Our solution was to set the last-access time of all existing entries to the migration timestamp. This approach preserves all cached content and establishes a consistent baseline, although it necessitates waiting one TTL period to realize the full benefits of eviction.</p> <p>We also ensured bidirectional compatibility - the original LRU implementation can read TLRU journal files by ignoring timestamp suffixes, enabling safe rollbacks if needed.</p> <p>Upon completing our TLRU implementation, we focused on determining optimal values for the three core attributes: <strong>TTL duration</strong>, <strong>minimum threshold</strong>, and <strong>maximum cache size</strong>. These parameters are crucial for balancing storage optimization and cache performance, requiring careful tuning based on real user behavior.</p> <h2 id="finding-optimal-configuration-values">Finding optimal configuration values</h2> <p>Finding optimal configuration values requires systematic experimentation and data-driven decision-making. Controlled experiments to compare the cache hit ratio with baseline LRU performance must be conducted.</p> <p><em>Note: Cache hit ratio, our key success metric, gauges efficiency by the percentage of requests served from cache versus requiring server downloads. Lower ratios lead to higher server costs and increased user data consumption.</em></p> <p>Our success criteria is for a cache hit ratio decrease of no more than 3 percentage points (pp) during the transition to TLRU. For instance, a decrease from 59% to 56% hit ratio would result in 7% increase in server requests. This threshold balances storage optimization with acceptable performance impact.</p> <p>To mitigate potential server cost impact from our maximum acceptable 3 pp cache hit ratio drop, we worked with the server team to optimize image delivery infrastructure, enabling a confident TLRU rollout without infrastructure cost concerns.</p> <h2 id="impact-and-results">Impact and results</h2> <p>After fully rolling out TLRU to production, we significantly optimized storage while preserving user experience. Post-implementation stabilization, the P95 total app size reduced by approximately 50 MB. This meant that 95% of our users experienced storage reduction up to 50 MB, with the top 5% seeing even greater savings.</p> <p>With over 100 million downloads of the Grab Android app, even conservative estimates show terabytes of storage reclaimed across all user devices worldwide. This translates to better device performance, especially on low-end devices, and improved user satisfaction.</p> <p>Critically, we maintained our success criteria: cache hit ratio stayed within target thresholds (no more than 3 pp decrease), with no increase in infrastructure costs. The seamless migration preserved all existing cache data without disruption.</p> <h2 id="conclusion">Conclusion</h2> <p>At Grab, we believe that every byte matters. Our users trust us with their device storage, and we take that responsibility seriously. The TLRU implementation exemplifies our commitment to user experience. We don’t just build features, we optimize them to ensure our app respects our users’ devices. The petabytes of storage reclaimed across millions of devices aren’t just a technical achievement; it’s a reflection of our dedication to creating a lighter, faster, more respectful mobile experience.</p> <p>The implementation demonstrates that meaningful improvements can be achieved through thoughtful modifications to existing, well-tested libraries. Our focus on backward compatibility and safe migration ensured zero disruption for Grab’s users, proving that user experience and technical innovation can coexist.</p> <h2 id="join-us">Join Us</h2> <p>Grab is Southeast Asia’s leading superapp, serving over 900 cities across eight countries (Cambodia, Indonesia, Malaysia, Myanmar, the Philippines, Singapore, Thailand, and Vietnam). Through a single platform, millions of users access mobility, delivery, and digital financial services, including ride-hailing, food delivery, payments, lending, and digital banking via GXS Bank and GXBank. Founded in 2012, Grab’s mission is to drive Southeast Asia forward by creating economic empowerment for everyone while delivering sustainable financial performance and positive social impact.</p> <p>Powered by technology and driven by heart, our mission is to drive Southeast Asia forward by creating economic empowerment for everyone. If this mission speaks to you, <a href="https://grab.careers/">join our team today</a>!</p> Fri, 06 Mar 2026 00:23:00 +0000 https://engineering.grab.com/reclaiming-tetabytes-optimizing-android-image-caching-with-tlru https://engineering.grab.com/reclaiming-tetabytes-optimizing-android-image-caching-with-tlru App disk Disk size Optimization Scalability Engineering Cursor at Grab: Adoption and impact <h2 id="adoption-overview">Adoption overview</h2> <p>The illustration below encapsulates how Cursor is scaled across Grab, achieving rapid and widespread adoption that accelerated software development and empowered non-technical teams to build solutions.</p> <div class="post-image-section"><figure> <img src="/img/cursor-at-grab/cursor-figure-1.png" alt="" style="width:70%" /><figcaption align="middle">Figure 1: Adoption overview of AI tool Cursor in Grab.</figcaption> </figure> </div> <h3 id="multi-tool-strategy">Multi-tool strategy</h3> <p>Grab embraces a multi-tool strategy for AI coding assistants. Rather than committing to a single solution, we experiment with multiple tools simultaneously, allowing us to compare outcomes and adopt what works. This approach keeps us flexible in a space that evolves quickly. We covered this philosophy in a <a href="https://www.grab.com/sg/inside-grab/stories/beyond-one-size-fits-all-why-grab-embraces-multiple-ai-coding-assistants/">previous post</a>.</p> <h3 id="growth">Growth</h3> <p>We introduced Cursor in late 2024 as one of several tools in our AI engineering toolkit. Adoption grew quickly: 98% of Tech Grabbers became monthly active users, and about 75% use it weekly. For comparison, Google’s <a href="https://services.google.com/fh/files/misc/2025_state_of_ai_assisted_software_development.pdf">2025 State of AI-Assisted Software Development</a> report highlights that even among high-performing teams, AI coding tool adoption seldom surpasses 70%. Notably, Cursor’s appeal extended beyond engineering, with non-technical teams incorporating it into their workflows.</p> <p>A standout metric is Cursor’s suggestion acceptance rate, which is around 50%, surpassing the industry average of 30%. This indicates two key insights: first, the suggestions are sufficiently relevant for engineers to accept them half of the time; second, engineers maintain a critical review process rather than accepting suggestions indiscriminately. We attribute this relevance to continuous feedback loops and environment-specific tuning, ensuring suggestions remain aligned with Grab’s codebase and conventions.</p> <h2 id="extent-of-adoption">Extent of adoption</h2> <p>Raw adoption figures don’t provide the complete picture. We aimed to determine whether engineers were truly incorporating Cursor into their daily workflows or merely experimenting with it sporadically.</p> <p>The data indicates genuine integration. Approximately half of Cursor users engage with it 10 or more consecutive days each month, with some teams achieving full adoption. Over a third of merge requests now incorporate Cursor in some capacity. Engineers actively share tips and workflows via a dedicated Slack channel, fostering an organic knowledge base.</p> <p>Across various teams, we’ve observed significant transitions from light usage to moderate and power user levels over the past six months.</p> <h2 id="engineer-utilization-patterns">Engineer utilization patterns</h2> <p>The most common patterns we see are unit test generation, code refactoring, cross-repository navigation, bug fixing, and automation of routine tasks like API scaffolding or commit messages.</p> <p>Test generation is particularly popular. Writing tests manually is tedious, and Cursor’s ability to generate and iteratively refine tests has become a standard part of many engineers’ workflows. Cross-repository navigation helps with onboarding and context-switching: engineers can ask Cursor questions about unfamiliar codebases rather than hunting through documentation.</p> <p>Qualitative feedback confirms what the adoption numbers suggest: tasks that took a full day to complete now take hours. Engineers report tackling refactors and test additions they would have otherwise skipped due to time pressure. Cursor doesn’t just speed up existing work; it makes previously impractical work feasible.</p> <h2 id="integration-with-grabs-stack">Integration with Grab’s stack</h2> <p>Integrating Cursor effectively at Grab required custom tooling. We built solutions for monorepo indexing to handle Grab’s scale and to distribute preconfigured rules that align Cursor’s suggestions with Grab-specific coding conventions. This integration ensures that Cursor understands our environment rather than offering generic suggestions.</p> <h2 id="whats-next">What’s next</h2> <p>Cursor is one tool in a broader toolkit. Our multi-tool strategy means we’re also investing in terminal-based workflows and <a href="https://engineering.grab.com/the-birth-of-grab-gpt">GrabGPT</a> for internal knowledge retrieval. Different tools suit different workflows. The aim is to empower users, not to restrict them.</p> <p>Beyond engineering, we’re expanding AI-assisted development to new personas. Our AI Upskilling workshops have trained several hundred Grabbers across five countries, including executive committee members and senior leaders who built and deployed their own apps. Non-engineers in Financial Planning and Analysis (FP&amp;A), Operations, and regional teams are now building tools to solve their own pain points.</p> <p>Our product design team has launched an initiative empowering designers to directly implement production fixes. Designers have successfully merged hundreds of merge requests, often with same-day turnaround, facilitating quicker iterations on UI fixes without the engineering queue delay. This process requires designers to be trained in Git fundamentals prior to gaining access, with initial reviews conducted by design managers.</p> <p>Cursor has become part of daily work at Grab. But adoption is only half the question — the other half is impact. We’ve been running a parallel effort to measure productivity effects rigorously, using fixed-effects regression to isolate Cursor’s contribution from other factors. Early findings show a dose-response relationship: productivity gains scale with usage intensity, and the effects hold up to statistical scrutiny.</p> <p>We will address the measurement methodology and present our findings in a subsequent post.</p> <h2 id="join-us">Join us</h2> <p>Grab is a leading superapp in Southeast Asia, operating across the deliveries, mobility and digital financial services sectors. Serving over 800 cities in eight Southeast Asian countries, Grab enables millions of people everyday to order food or groceries, send packages, hail a ride or taxi, pay for online purchases or access services such as lending and insurance, all through a single app. Grab was founded in 2012 with the mission to drive Southeast Asia forward by creating economic empowerment for everyone. Grab strives to serve a triple bottom line – we aim to simultaneously deliver financial performance for our shareholders and have a positive social impact, which includes economic empowerment for millions of people in the region, while mitigating our environmental footprint.</p> <p>Powered by technology and driven by heart, our mission is to drive Southeast Asia forward by creating economic empowerment for everyone. If this mission speaks to you, <a href="https://grb.to/cursoratgrab1">join our team</a> today!</p> Thu, 29 Jan 2026 00:23:00 +0000 https://engineering.grab.com/cursor-at-grab-adoption-and-impact https://engineering.grab.com/cursor-at-grab-adoption-and-impact AI Engineering Docker lazy loading at Grab: Accelerating container startup times <h2 id="introduction">Introduction</h2> <p>At Grab, we’ve been exploring ways to dramatically reduce container startup times for our data platforms. Large container images for services like Airflow and Spark Connect were taking minutes to download, causing slow cold starts and poor auto-scaling performance. This blog post shares our journey implementing Docker image lazy loading using eStargz and Seekable OCI (SOCI) technologies, the results we achieved, and the lessons learned along the way.</p> <h2 id="results-the-numbers-speak-for-themselves">Results: The numbers speak for themselves</h2> <h3 id="benchmark-results">Benchmark results</h3> <p>Our initial testing on fresh nodes (nodes without cached images) showed dramatic improvements in image pull times as shown in <strong>Figure 1</strong>.</p> <div class="post-image-section"><figure> <img src="/img/docker-lazy-loading/figure-1.png" alt="" style="width:70%" /><figcaption align="middle">Figure 1. Table of results.</figcaption> </figure> </div> <p>The key advantage of lazy loading is the reduction in image pull time, especially on “fresh” nodes that do not have the image cached. By analyzing detailed pod events, we can see the precise impact of using the stargz snapshotter.</p> <p>During our SOCI benchmark testing, we observed an important distinction between SOCI and eStargz: <strong>SOCI maintains the same application startup time as standard images, while eStargz takes longer</strong>. For example, with Airflow, both overlayFS and SOCI achieved 5.0 seconds startup time, while eStargz took 25.0 seconds. This demonstrates that lazy loading doesn’t eliminate download time; it redistributes it. SOCI’s approach of maintaining separate indexes allows it to optimize the download-to-startup time trade-off more effectively, keeping application startup performance on par with standard images while still dramatically reducing image pull time.</p> <h2 id="production-performance">Production performance</h2> <p>The production deployment of SOCI lazy loading has delivered significant, measurable improvements across our data platforms. Both Airflow and Spark Connect now experience 30-40% faster startup times, directly improving our ability to handle traffic spikes and scale efficiently. These improvements translate to better auto-scaling responsiveness, reduced resource waste during initialization, and improved user experience for data processing workloads. The sustained performance gains observed over time demonstrate that lazy loading is a stable, production-ready optimization that delivers consistent value.</p> <p><strong>Figure 2 and 3</strong> illustrates the P95 startup time improvements for both services:</p> <div class="post-image-section"><figure> <img src="/img/docker-lazy-loading/figure-2.png" alt="" style="width:70%" /><figcaption align="middle">Figure 2. Production results: Airflow P95 startup time. </figcaption> </figure> </div> <div class="post-image-section"><figure> <img src="/img/docker-lazy-loading/figure-3.png" alt="" style="width:70%" /><figcaption align="middle">Figure 3. Production results: Spark Connect P95 startup time.</figcaption> </figure> </div> <p>It is important to note that P95 startup time includes both the image download/pull time and the application startup time itself. This metric captures the entire system performance for both cold and hot starts on fresh and hot nodes, showing the overall system improvement rather than just cold start performance.</p> <p>During the production deployment and monitoring, we gained valuable insights on SOCI configuration tuning. Following AWS’s recommended configuration from their blog on <a href="https://aws.amazon.com/blogs/containers/introducing-seekable-oci-parallel-pull-mode-for-amazon-eks/">Introducing Seekable OCI: Parallel Pull Mode for Amazon EKS</a>, we optimized our SOCI snapshotter settings:</p> <ul> <li> <p>Increased <em>max_concurrent_downloads_per_image</em> from 5 to 10.</p> </li> <li> <p>Increased <em>max_concurrent_unpacks_per_image</em> from 3 to 10.</p> </li> <li> <p>Increased <em>concurrent_download_chunk_size</em> from 8MB to 16MB (aligning with AWS’s recommendation for Elastic Container Registry (ECR)).</p> </li> </ul> <p>This configuration tuning led to a significant performance improvement: <strong>image download time on a fresh node was reduced from 60 seconds to 24 seconds, representing a 60% improvement</strong>. The key lesson here is that default SOCI configurations may not be optimal for all environments, and tuning these parameters based on your infrastructure (especially when using ECR) can yield substantial gains.</p> <h2 id="technical-background-how-docker-lazy-loading-works">Technical background: How Docker lazy loading works</h2> <h3 id="container-root-filesystem-rootfs-and-file-organization">Container root filesystem (rootfs) and file organization</h3> <p>A container’s root filesystem, or rootfs, is the directory structure that the container sees as its root <code class="language-plaintext highlighter-rouge">(/)</code>. It contains all the files and directories necessary for an application to run, including the application itself, its dependencies, system libraries, and configuration files. It’s an isolated filesystem, separate from the host machine’s filesystem.</p> <p>The rootfs is built from a series of read-only layers that come from the container image. Each instruction in an image’s Dockerfile creates a new layer, representing a set of filesystem changes. When a container is launched, a new writable layer, often called the “container layer,” is added on top of the stack of read-only image layers. Any changes made to the running container, such as writing new files or modifying existing ones, are written to this writable layer. The underlying image layers remain untouched. This is known as a copy-on-write (CoW) mechanism.</p> <p>In containerd, a snapshotter is a plugin responsible for managing container filesystems. Its primary job is to take the layers of an image and assemble them into a rootfs for a container. The default snapshotter in containerd is <strong>overlayFS</strong>, which uses the Linux kernel’s OverlayFS driver to efficiently stack layers. To assemble the rootfs, the overlayFS snapshotter creates a “merged” view of the read-only image layers:</p> <div class="post-image-section"><figure> <img src="/img/docker-lazy-loading/figure-4.png" alt="" style="width:70%" /><figcaption align="middle">Figure 4. How OverlayFS assembles the container filesystem.</figcaption> </figure> </div> <ul> <li> <p><strong>lowerdir</strong>: The read-only image layers are used as the lowerdir in OverlayFS. These are the immutable layers from the container image.</p> </li> <li> <p><strong>upperdir</strong>: A new, empty directory is created to be the upperdir. This is the writable layer for the container where any changes are stored.</p> </li> <li> <p><strong>merged</strong>: The merged directory is the unified view of the lowerdir and upperdir. This is what is presented to the container as its rootfs.</p> </li> </ul> <p>When a container reads a file, it’s read from the merged view. When a container writes a file, it’s written to the upperdir using a copy-on-write mechanism. This is an efficient way to manage container filesystems, as it avoids duplicating files and allows for fast container startup.</p> <h3 id="the-problem-traditional-container-image-pull">The problem: Traditional container image pull</h3> <p>To understand the benefits of lazy loading, we first need to understand the traditional container image pull process:</p> <ol> <li> <p><strong>Download layers</strong>: The container runtime downloads all layer tarballs that make up the image.</p> </li> <li> <p><strong>Unpack layers</strong>: Each layer is unpacked and extracted onto the host’s disk.</p> </li> <li> <p><strong>Create snapshot</strong>: The snapshotter combines these layers into a single, unified filesystem, known as the container’s rootfs.</p> </li> <li> <p><strong>Start container</strong>: Only after all layers are downloaded and unpacked can the container start.</p> </li> </ol> <p>This process is slow, especially for large images, as the entire image must be present on the host before the container can launch.</p> <h3 id="the-solution-remote-snapshotter">The solution: Remote snapshotter</h3> <p>To address the slow startup issue with large images, we use a <strong>remote snapshotter</strong> solution. A remote snapshotter is a special type of snapshotter that doesn’t require all image data to be locally present. Instead of downloading and unpacking all the layers, it creates a “snapshot” that points to the remote location of the data (like a container registry). The actual file content is then fetched on-demand when the container tries to read a file for the first time.</p> <p>While a traditional snapshotter like overlayFS uses directories on the local disk as its lowerdir, a remote snapshotter creates a virtual lowerdir that is backed by the remote registry. This is typically done using FUSE (Filesystem in Userspace). The remote snapshotter creates a FUSE filesystem that presents the contents of the remote layer as if it were a local directory. This FUSE mount is then used as the lowerdir for the overlayFS driver. This allows the remote snapshotter to integrate with the existing overlayFS infrastructure while adding the capability of lazy-loading data from a remote source.</p> <p>There are two main formats that enable remote snapshotters: <strong>eStargz</strong> and <strong>SOCI</strong>.</p> <h3 id="estargz-format">eStargz format</h3> <p>eStargz is a backward-compatible extension of the standard OCI <code class="language-plaintext highlighter-rouge">tar.gz</code> layer format. It has several key features that enable lazy loading:</p> <ul> <li> <p><strong>Individually compressed files</strong>: Each file within the layer (and even chunks of large files) is compressed individually. This is the key that allows for random access to file contents.</p> </li> <li> <p><strong>TOC (table of contents)</strong>: A JSON file named <code class="language-plaintext highlighter-rouge">stargz.index.json</code> is located at the end of the layer. This TOC contains metadata for every file, including its name, size, and, most importantly, its offset within the layer blob.</p> </li> <li> <p><strong>Footer</strong>: A small footer at the very end of the layer contains the offset of the TOC, allowing it to be easily located by reading only the last few bytes of the layer.</p> </li> <li> <p><strong>Chunking and verification</strong>: Large files can be broken down into smaller chunks, each with its own entry in the TOC. Each chunk also has a chunkDigest in its TOC entry, allowing for independent verification of each downloaded piece of data.</p> </li> <li> <p><strong>Prefetch landmark</strong>: A special file, <code class="language-plaintext highlighter-rouge">.prefetch.landmark</code>, can be placed in the layer to mark the end of “prioritized files”. This allows the snapshotter to intelligently prefetch the most important files for the container’s workload.</p> </li> </ul> <p>The stargz snapshotter uses the eStargz format to enable lazy loading. Here’s how it works:</p> <ol> <li> <p><strong>Mount request</strong>: When containerd calls the Mount function, it’s the main entry point for creating a new filesystem for a layer.</p> </li> <li> <p><strong>Resolve and read TOC</strong>: The snapshotter fetches the layer’s footer, then fetches the <code class="language-plaintext highlighter-rouge">stargz.index.json</code> TOC from the remote registry. This TOC contains all the file metadata needed to create a virtual filesystem.</p> </li> <li> <p><strong>Mount FUSE filesystem</strong>: With the TOC in memory, the snapshotter creates a virtual filesystem using FUSE. The container can now start, as it has a valid rootfs, even though most of the file content has not been downloaded.</p> </li> <li> <p><strong>On-demand fetching</strong>: When the container performs a file operation like <code class="language-plaintext highlighter-rouge">read()</code>, the FUSE filesystem intercepts the call. The snapshotter checks a local disk cache for the requested bytes. If the data is not cached, it issues an HTTP Range request to the container registry to download only the required chunk of the layer.</p> </li> <li> <p><strong>Remote fetching and caching</strong>: The downloaded data is returned to the container and also written to the local cache for subsequent reads.</p> </li> <li> <p><strong>Prefetching for optimization</strong>: After the FUSE filesystem is mounted, a background goroutine begins downloading the prioritized files (up to the <em>.prefetch.landmark</em>) and can also be configured to download the entire rest of the layer in the background.</p> </li> </ol> <p>For a deeper understanding of the eStargz format and stargz snapshotter, see the <a href="https://github.com/containerd/stargz-snapshotter/blob/main/docs/overview.md">stargz-snapshotter overview documentation</a>.</p> <h3 id="soci-format">SOCI format</h3> <p>SOCI is a technology open sourced by AWS that enables containers to launch faster by lazily loading the container image. SOCI works by creating an index (SOCI Index) of the files within an existing container image. SOCI borrows some of the design principles from stargz-snapshotter but takes a different approach:</p> <ul> <li> <p><strong>Separate index</strong>: A SOCI index is generated separately from the container image and is stored in the registry as an OCI Artifact, linked back to the container image by OCI Reference Types.</p> </li> <li> <p><strong>No image conversion</strong>: This means that the container images do not need to be converted, image digests do not change, and image signatures remain valid.</p> </li> <li> <p><strong>Native Bottlerocket support</strong>: SOCI is natively supported on Bottlerocket OS.</p> </li> </ul> <p>For a deeper understanding of the SOCI format, see the <a href="https://github.com/awslabs/soci-snapshotter/blob/main/docs/index.md">soci-snapshotter documentation</a>.</p> <h2 id="building-and-deploying-lazy-loaded-images">Building and deploying lazy-loaded images</h2> <h3 id="setting-up-snapshotters-in-eks">Setting up snapshotters in EKS</h3> <p>When using EKS with containerd as the container runtime, you can configure remote snapshotters to enable lazy loading. Here’s how to set them up:</p> <p><strong>For stargz-snapshotter (eStargz)</strong>: You need to install the <code class="language-plaintext highlighter-rouge">containerd-stargz-grpc</code> service first, then register it as a proxy plugin in containerd’s configuration:</p> <pre><code class="language-textproto"># /etc/containerd/config.toml [proxy_plugins] [proxy_plugins.stargz] type = "snapshot" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" </code></pre> <p>For detailed installation instructions, see the <a href="https://github.com/containerd/stargz-snapshotter/blob/main/docs/INSTALL.md">stargz-snapshotter installation documentation</a>. The setup can be baked into an AMI for production use or tested via user data from node bootstrap scripts.</p> <p><strong>For SOCI snapshotter (Bottlerocket)</strong>: On Bottlerocket nodes, enable the SOCI snapshotter via user data:</p> <pre><code class="language-textproto"># Enable SOCI snapshotter [settings.container-runtime] snapshotter = "soci" </code></pre> <p>SOCI is natively supported on Bottlerocket, so no additional daemon installation is required.</p> <h3 id="building-lazy-loaded-images">Building lazy-loaded images</h3> <p>eStargz images can be built natively using Docker Buildx by setting the output compression to <code class="language-plaintext highlighter-rouge">estargz</code>:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker buildx build <span class="nt">--platform</span> linux/amd64 <span class="nt">--output</span> <span class="nb">type</span><span class="o">=</span>registry,oci-mediatypes<span class="o">=</span><span class="nb">true</span>,compression<span class="o">=</span>estargz,force-compression<span class="o">=</span><span class="nb">true</span> <span class="nt">--tag</span> <span class="nv">$ECR_REGISTRY</span>/airflow:<span class="nv">$TAG</span> <span class="nb">.</span> </code></pre></div></div> <p>SOCI doesn’t require rebuilding images; you only need to generate a SOCI index for existing images. Since Docker doesn’t natively support SOCI index generation yet, workaround solutions include using the <a href="https://awslabs.github.io/cfn-ecr-aws-soci-index-builder/#_overview">AWS SOCI Index Builder Using Lambda Functions</a> or integrating SOCI index generation into your CI/CD pipeline as described in this <a href="https://pabis.eu/blog/2025-06-17-Faster-ECS-Startup-SOCI-Index-GitLab-Pipeline.html">blog post</a>.</p> <h2 id="key-takeaway-why-we-chose-soci">Key takeaway: Why we chose SOCI</h2> <p>We started our exploration with eStargz but ultimately chose SOCI for production deployment. The key reason is scalability and alignment with our strategy to use Bottlerocket OS for enhancing Kubernetes pod startup and security. SOCI is natively supported by Bottlerocket, which means service teams don’t need to set up and maintain the more complicated stargz snapshotter across all EKS clusters. This makes the implementation easier to maintain and provides better support from AWS.</p> <p>Additionally, we learned that lazy loading doesn’t eliminate the time required to download image data; it redistributes it from startup time to runtime. While this dramatically improves cold start performance, it’s important to monitor application performance closely and tune configuration parameters based on your workload and infrastructure. We achieved a 60% improvement by optimizing SOCI’s parallel pull mode settings, demonstrating the value of proper configuration tuning.</p> <h2 id="conclusion">Conclusion</h2> <p>Docker image lazy loading with SOCI offers a significant opportunity to improve the performance and efficiency of our services at Grab. Our testing and production deployments have shown:</p> <ul> <li> <p>4x faster image pull times on fresh nodes.</p> </li> <li> <p>29-34% improvement in P95 startup times for production workloads.</p> </li> <li> <p>60% improvement in image download times with proper configuration tuning.</p> </li> </ul> <p>The implementation path is clear, low-risk, and builds on proven components. This technology is production-ready, and we’re continuing to scale it across more services.</p> <h3 id="references">References</h3> <ul> <li> <p><strong>Databricks:</strong> <a href="https://www.databricks.com/blog/2021/09/08/booting-databricks-vms-7x-faster-for-serverless-compute.html">Booting Databricks VMs 7x Faster for Serverless Compute</a> - Industry case study showing how major tech companies achieve fast container startup at scale</p> </li> <li> <p><strong>BytePlus:</strong> <a href="https://docs.byteplus.com/en/docs/vke/Container-image-lazy-loading-solution">Container Image Lazy Loading Solution</a> - Enterprise implementation guide for lazy loading in production Kubernetes environments</p> </li> <li> <p><strong>AWS:</strong> <a href="https://aws.amazon.com/blogs/containers/introducing-seekable-oci-parallel-pull-mode-for-amazon-eks/">Introducing Seekable OCI: Parallel Pull Mode for Amazon EKS</a> - AWS’s guide to SOCI configuration and optimization</p> </li> </ul> <h2 id="join-us">Join us</h2> <p>Grab is a leading superapp in Southeast Asia, operating across the deliveries, mobility and digital financial services sectors. Serving over 800 cities in eight Southeast Asian countries, Grab enables millions of people everyday to order food or groceries, send packages, hail a ride or taxi, pay for online purchases or access services such as lending and insurance, all through a single app. Grab was founded in 2012 with the mission to drive Southeast Asia forward by creating economic empowerment for everyone. Grab strives to serve a triple bottom line – we aim to simultaneously deliver financial performance for our shareholders and have a positive social impact, which includes economic empowerment for millions of people in the region, while mitigating our environmental footprint.</p> <p>Powered by technology and driven by heart, our mission is to drive Southeast Asia forward by creating economic empowerment for everyone. If this mission speaks to you, <a href="https://grb.to/gebdockerlazyloading">join our team</a> today!</p> Wed, 21 Jan 2026 00:23:00 +0000 https://engineering.grab.com/docker-lazy-loading https://engineering.grab.com/docker-lazy-loading Database Engineering From deployment slop to production reality: How BriX bridges the gap with enterprise-grade AI infrastructure <h2 id="abstract">Abstract</h2> <p>You’ve vibe-coded an AI assistant that’s a game-changer for your team. It works perfectly on your laptop. But when you try to deploy it company-wide, everything falls apart.</p> <p>This is what is known as “deployment slop”—the messy reality when quick AI prototypes hit the enterprise world. Your tool suddenly becomes unreliable, insecure, and impossible to maintain. Different teams run different versions. Security flags it. IT won’t touch it. Your innovation dies.</p> <p><strong>BriX solves this</strong>. It’s a platform that takes your working AI prototype and makes it production-ready—without forcing you to become a full-stack developer. BriX handles the hard parts such as security, scaling, and data connections, so you can focus on building great tools. Switch between AI models like Claude or GPT with a click. Connect securely to your company’s data sources. Deploy once, and it just works—for everyone.</p> <p>This article shows how BriX transforms AI deployment from an engineering bottleneck into a configuration task, enabling domain experts to ship enterprise-grade AI tools in days instead of months.</p> <h2 id="introduction">Introduction</h2> <p>Building AI tools has never been easier. With ChatGPT, Claude, and other Large Language Models (LLMs), anyone can prototype a useful AI assistant in an afternoon. Data analysts build metric query tools; product managers create research assistants. This rapid experimentation—”vibe coding”—has sparked innovation across organizations.</p> <p>But then comes the hard part: <em>deployment</em>.</p> <p>That brilliant tool you built on your laptop? It works great for you. But when your boss asks you to “roll it out to the whole company,” you hit a wall. Suddenly you need:</p> <ul> <li>Security reviews (Is it leaking sensitive data?)</li> <li>Reliability guarantees (What happens when 500 people use it at once?)</li> <li>Access controls (Who can see what data?)</li> <li>Audit trails (Who asked what, and when?)</li> <li>Consistent behavior (Why does it give different answers to different people?)</li> </ul> <p>Most builders aren’t DevOps engineers. They’re domain experts who had a good idea. So these tools either:</p> <ul> <li>Never get deployed (innovation dies in a Jupyter notebook); or</li> <li>Get deployed badly (creating “Deployment Slop”—a mess of insecure, unreliable scripts).</li> </ul> <h3 id="the-three-failure-modes-of-deployment-slop">The three failure modes of deployment slop</h3> <h4 id="the-chaos-problem-everyones-running-a-different-version">The chaos problem: Everyone’s running a different version</h4> <p>Marketing copies your script and tweaks the prompts. Finance changed the model from GPT-4 to Claude because it’s cheaper. Sales adds their own data sources. Within weeks, you have:</p> <ul> <li>Five different versions of “the same tool”.</li> <li>Wildly different answers to the same question.</li> <li>No one knows which version is “correct”.</li> <li>Teams making decisions based on inconsistent data.</li> </ul> <p><strong>Potential risk</strong>: A senior executive receiving conflicting answers from different teams, resulting in a loss of trust.</p> <h4 id="the-reliability-problem-it-works-until-it-doesnt">The reliability problem: It works until it doesn’t</h4> <p>Your laptop script was built for one user (you). Now 50 people are using it simultaneously. The result:</p> <ul> <li>Timeouts and crashes during peak hours.</li> <li>No error handling (users see cryptic Python stack traces).</li> <li>Rate limits hit on API calls.</li> <li>No monitoring or alerts when things break.</li> <li>You become the “on-call” support person for a side project.</li> </ul> <p><strong>Potential risk</strong>: The tool fails during a critical metric review leaving folks to find the solution manually.</p> <h4 id="the-security-problem-accidental-data-leaks">The security problem: Accidental data leaks</h4> <p>Your prototype connects directly to production databases. It has your personal credentials hardcoded. There’s no:</p> <ul> <li>Access control (everyone sees all data, including sensitive info).</li> <li>Audit trail (no record of who queried what).</li> <li>Data governance (PII might be exposed).</li> <li>Compliance review (legal and security teams don’t even know it exists).</li> </ul> <p><strong>Potential risk</strong>: An employee inadvertently querying PII, resulting in a potential breach.</p> <h3 id="who-gets-hit-hardest">Who gets hit hardest?</h3> <p>This problem is especially painful for semi-technical builders—the domain experts who understand the business problem but aren’t DevOps engineers:</p> <ul> <li>Product Managers who write SQL but not Kubernetes configs.</li> <li>Data Analysts who know Python but not cloud security.</li> <li>Marketing Ops who build dashboards but not CI/CD pipelines.</li> <li>HR Analytics who understand people data but not infrastructure scaling.</li> </ul> <p>The traditional solution is to “hand it to Engineering,” but they are backlogged for months. By the time they rebuild your tool “properly,” the business need has changed.</p> <h2 id="solution-enter-brix-from-prototype-to-production-in-days-not-months">Solution: Enter BriX: From prototype to production in days, not months</h2> <p>BriX is a platform that solves the deployment problem by centralizing all the hard infrastructure work. Instead of forcing every builder to become a DevOps expert, BriX provides the production-ready foundation so you can focus on building great AI tools.</p> <p>The core insight: Deployment doesn’t have to be an engineering problem. It can be a configuration problem.</p> <h3 id="what-brix-does">What BriX does</h3> <p>Think of BriX as the “production layer” for AI tools. You bring your working prototype. BriX handles security, scaling, data connections, monitoring, audit trails, and consistent behavior across teams.</p> <p>You configure. BriX deploys.</p> <div class="post-image-section"><figure> <img src="/img/brix/brix-infrastructure.png" alt="" style="width:70%" /><figcaption align="middle">Figure 1. BriX infrastructure</figcaption> </figure> </div> <h3 id="the-three-core-capabilities">The three core capabilities</h3> <h4 id="choose-your-ai-model-model-agnosticism">Choose your AI model (Model agnosticism)</h4> <p>Different tasks need different models. BriX lets you switch between models with a dropdown—Claude, GPT, Gemini, or others. Test which works best. Change models without rewriting code. Optimize for cost vs. performance.</p> <p>Example: Your finance tool uses GPT-4 for complex analysis, but a new better model is available. Change it in BriX with one click—no code changes needed.</p> <div class="post-image-section"><figure> <img src="/img/brix/model-selection-interface.png" alt="" style="width:90%" /><figcaption align="middle">Figure 2. Model selection interface</figcaption> </figure> </div> <h4 id="connect-to-enterprise-data-securely-model-context-protocols">Connect to enterprise data securely (Model Context Protocols)</h4> <p>This is where BriX really shines. Your AI tool needs data—metrics, customer info, documentation. But connecting to enterprise systems securely is hard.</p> <p>Model Context Protocols (MCPs) are BriX’s solution. Think of them as secure, pre-built connectors to your company’s data sources.</p> <p>Why MCPs matter:</p> <ul> <li>Security built-in: No hardcoded credentials, proper access controls.</li> <li>Certified data: Connect only to approved, governed data sources.</li> <li>No custom integration: Pre-built connectors, not custom API code.</li> <li>Audit trails: Every query is logged automatically.</li> </ul> <p><strong>Example</strong>: Your marketing tool can query the metrics system to get conversion rates, search the knowledge base for campaign guidelines, and pull customer data from the data lake —all through secure, governed connections.</p> <p><strong>Technical note</strong>: MCPs use a standardized protocol, so adding new data sources doesn’t require rebuilding your tool. BriX handles the complexity.</p> <div class="post-image-section"><figure> <img src="/img/brix/chat-user-interface.png" alt="" style="width:90%" /><figcaption align="middle">Figure 3. BriX chat user interface</figcaption> </figure> </div> <h4 id="ensure-consistent-behavior-system-prompts-and-context">Ensure consistent behavior (System prompts and context)</h4> <p>Remember the “chaos problem” where everyone runs different versions? BriX solves this with centralized configurations by allowing you to lock it down for the users:</p> <ul> <li>System prompts: Define your AI’s personality, tone, and guardrails once.</li> <li>Context files: Upload reference documents that every instance uses.</li> <li>Global enforcement: All users get the same behavior automatically.</li> </ul> <p><strong>Example</strong>: Your customer support tool has a system prompt that says “Always be empathetic, never make promises about refunds, escalate to humans for complaints.” Every support agent’s AI follows these rules—no exceptions.</p> <div class="post-image-section"><figure> <img src="/img/brix/builder-view-1.png" alt="" style="width:80%" /> </figure> </div> <div class="post-image-section"><figure> <img src="/img/brix/builder-view-2.png" alt="" style="width:80%" /><figcaption align="middle">Figure 4. The builder’s view</figcaption> </figure> </div> <h4 id="additional-feature-flexible-interfaces-and-collaboration">Additional feature: Flexible interfaces and collaboration</h4> <p>Beyond the core infrastructure, BriX offers flexible ways to consume these tools. BriX goes beyond conversational interfaces—you can host custom UIs built with any frontend framework while BriX handles the AI backend. Users can also generate and share analyses as persistent reports, turning individual queries into institutional knowledge accessible across teams via shareable links—complete with data, visualizations, and AI insights.</p> <div class="post-image-section"><figure> <img src="/img/brix/share-feature-interface.png" alt="" style="width:90%" /><figcaption align="middle">Figure 5. Share feature interface</figcaption> </figure> </div> <h3 id="the-brix-workflow-a-real-example">The BriX workflow: A real example</h3> <p>Let’s see how a product manager would use BriX:</p> <p><strong>Step 1: Upload your prototype</strong></p> <ul> <li>You’ve built a Jupyter notebook that queries metrics and generates reports.</li> <li>Upload it to BriX (or connect your GitHub repo).</li> </ul> <p><strong>Step 2: Configure (Not code)</strong></p> <ul> <li>Choose your AI model: Claude 4.5 Sonnet</li> <li>Connect data sources: Midas (metrics), Hubble (data lake)</li> <li>Set system prompt: “You’re a data analyst. Always cite sources. Format numbers with commas.”</li> <li>Upload context: Your company’s metrics definitions guide.</li> </ul> <p><strong>Step 3: Lock</strong></p> <ul> <li>Lock all the configurations of your BriX.</li> <li>Share with your team.</li> </ul> <div class="post-image-section"><figure> <img src="/img/brix/brix-landing-page.png" alt="" style="width:90%" /><figcaption align="middle">Figure 6. BriX landing page</figcaption> </figure> </div> <div class="post-image-section"><figure> <img src="/img/brix/user-view-1.png" alt="" style="width:80%" /> </figure> </div> <div class="post-image-section"><figure> <img src="/img/brix/user-view-2.png" alt="" style="width:80%" /><figcaption align="middle">Figure 7. The user’s view (Locks and edit not available)</figcaption> </figure> </div> <p><strong>Step 4: It just works</strong></p> <ul> <li>Certification by design with Brick Quality residing with the brick admin.</li> <li>Focused use cases have specific system prompts, context - minimizing hallucination concerns.</li> <li>People can use it simultaneously (BriX handles scaling).</li> <li>Everyone gets consistent answers (same model, same prompts).</li> <li>All queries are logged (audit trail automatic).</li> <li>The security team is happy (proper access controls).</li> <li>You’re not on-call (BriX monitors and alerts).</li> </ul> <p>Time to production: 3 Days, not 3 months.</p> <h3 id="under-the-hood-the-brix-architecture">Under the hood: The BriX architecture</h3> <p>BriX is built on a synchronous streaming architecture—a design that prioritizes real-time responsiveness without sacrificing enterprise security. Think of it like a live sports broadcast: you see the action as it happens, not a delayed replay.</p> <div class="post-image-section"><figure> <img src="/img/brix/brix-architecture.png" alt="" style="width:90%" /><figcaption align="middle">Figure 8. BriX architecture</figcaption> </figure> </div> <p>Here’s how a single user request flows through the system, from question to answer.</p> <p><strong>The request journey: Six layers</strong></p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>User Question ↓ [1] The Frontend — Real-Time Streaming ↓ [2] The Gateway — FastAPI Backend ↓ [3] The Brain — LangGraph Orchestration ↓ [4] Memory — Hot and Cold Storage ↓ [5] Security — Identity Propagation ("On-Behalf-Of" Flow) ↓ [6] Data Processing — Full Context, Not Fragments ↓ Response streams back to user in real-time </code></pre></div></div> <p>Let’s break down each layer.</p> <h4 id="layer-1-the-frontend--real-time-streaming">Layer 1: The frontend — Real-time streaming</h4> <ul> <li><strong>Technology</strong>: React (TypeScript)</li> <li><strong>User experience</strong>: ChatGPT-style interface</li> </ul> <p>The User types a question: “What’s our conversion rate in Singapore last month?”</p> <p>The frontend opens a persistent connection to BriX servers. As the AI processes the question, updates stream back instantly:</p> <ul> <li>“🤔 Thinking…”</li> <li>“📊 Querying metrics database…”</li> <li>“✅ Found 3 relevant data points…” [Final answer appears]</li> </ul> <p>Why streaming matters:</p> <table class="table"> <thead> <tr> <th>Traditional approach</th> <th>BriX approach</th> </tr> </thead> <tbody> <tr> <td rowspan="1">❌ User waits 30 seconds, sees nothing, then gets full answer (feels broken).</td> <td>✅ User sees progress every second (feels responsive and trustworthy).</td> </tr> </tbody> </table> <p>Technical implementation: Server-Sent Events (SSE) for real-time updates without WebSocket complexity.</p> <h4 id="layer-2-the-gateway--fastapi-backend">Layer 2: The Gateway — FastAPI backend</h4> <ul> <li><strong>Technology</strong>: FastAPI (Python)</li> <li><strong>Role</strong>: Central traffic controller</li> </ul> <p><strong>What it does</strong>:</p> <ul> <li>Receives all incoming requests</li> <li>Authenticates users (checks SSO tokens)</li> <li>Routes requests to the appropriate agent</li> <li>Manages rate limiting (prevents abuse)</li> <li>Handles errors gracefully</li> </ul> <p><strong>Why FastAPI?</strong></p> <ul> <li>⚡ Fast (async/await for concurrent requests)</li> <li>🔒 Secure (built-in authentication)</li> <li>📈 Scalable (handles thousands of concurrent users)</li> </ul> <p><strong>Layer 3: The Brain — LangGraph orchestration</strong></p> <ul> <li><strong>Technology</strong>: LangGraph (AI workflow framework)</li> <li><strong>Role</strong>: The “main agent” that coordinates everything.</li> </ul> <p>Think of LangGraph as a smart router that understands intent and delegates work.</p> <p><strong>Example flow</strong>:</p> <p><strong><em>User asks</em></strong>: “Compare our Singapore and Malaysia conversion rates, then explain why they differ”.</p> <p><strong><em>LangGraph analyzes the question</em></strong>:</p> <ul> <li>Task 1: Query metrics (needs Midas MCP)</li> <li>Task 2: Compare data (needs calculation)</li> <li>Task 3: Explain differences (needs context/knowledge base)</li> </ul> <p>LangGraph delegates to specialized “MCPs”:</p> <ul> <li>Midas MCP: Queries Midas for conversion data</li> <li>LLM Agent: Calculates the difference</li> <li>Glean MCP: Searches knowledge base for regional factors</li> </ul> <p>LangGraph synthesizes: Combines results into coherent answer Why modular “Bricks”?</p> <ul> <li>✅ Reliability: Each Brick is specialized (fewer hallucinations)</li> <li>✅ Maintainability: Update one Brick without breaking others</li> <li>✅ Extensibility: Add new Bricks for new use cases</li> </ul> <h4 id="layer-4-memory--hot-and-cold-storage">Layer 4: Memory — Hot and cold storage</h4> <p>BriX uses a two-tier memory system to balance speed and durability:</p> <p><strong><em>Hot memory (Redis)</em></strong>:</p> <ul> <li>⚡ Ultra-fast: In-memory storage (microsecond access).</li> <li>🔄 Session management: Tracks active conversations.</li> <li>🔒 Distributed locks: Prevents race conditions when multiple requests happen simultaneously.</li> <li>💨 Temporary: Data expires after session ends.</li> </ul> <p><strong><em>Cold memory (PostgreSQL)</em></strong>:</p> <ul> <li>💾 Persistent: Data stored permanently</li> <li>📜 Audit trail: Every query, response, and action logged</li> <li>🔍 Searchable: Users can search past conversations</li> <li>📊 Analytics: Track usage patterns and performance</li> </ul> <p><strong><em>Example scenario</em></strong>:</p> <ul> <li>You ask BriX a question → Hot memory tracks your active session</li> <li>You close the browser → Session data moves to cold memory</li> <li>You return tomorrow → BriX loads your history from cold memory</li> <li>You continue the conversation → New session in hot memory</li> </ul> <p><strong><em>Result</em></strong>: Fast responses + complete history + full auditability</p> <h4 id="layer-5-security--identity-propagation-on-behalf-of-flow">Layer 5: Security — Identity propagation (“On-Behalf-Of” flow)</h4> <p>This is where BriX’s security model shines. Instead of using a single “service account” to access all data, BriX uses your credentials for every query.</p> <p><strong><em>How it works</em></strong>:</p> <p><strong>Step 1: Authentication (Login)</strong></p> <ul> <li>You log in via SSO (e.g., Okta, Azure AD)</li> <li>BriX receives a secure token that represents your identity</li> <li>This token includes your permissions (what data you can access)</li> </ul> <p><strong>Step 2: Identity propagation (Query execution)</strong></p> <ul> <li>You ask: “Show me customer revenue data”</li> <li>BriX doesn’t use its own credentials to query the database</li> <li>Instead, BriX carries your token to the data source</li> <li>The data source checks: “Does this user have permission to see revenue data?” <ul> <li>If yes → Returns data</li> <li>If no → Access denied</li> </ul> </li> </ul> <p><strong>Step 3: Audit trail</strong></p> <ul> <li>Every query is logged with: <ul> <li><strong>Who</strong> asked (your user ID)</li> <li><strong>What</strong> they asked (the question)</li> <li><strong>What data</strong> was accessed (the query)</li> <li><strong>When</strong> it happened (timestamp)</li> </ul> </li> </ul> <p><strong><em>Why this matters</em></strong>:</p> <table class="table"> <thead> <tr> <th>Traditional approach</th> <th>BriX approach</th> </tr> </thead> <tbody> <tr> <td rowspan="1">❌ Service account has access to ALL data.</td> <td>✅ Each user only sees their authorized data.</td> </tr> <tr> <td rowspan="1">❌ Can't tell who accessed what.</td> <td>✅ Complete audit trail per user.</td> </tr> <tr> <td rowspan="1">❌ Security team nervous about AI tools.</td> <td>✅ Security team approves (same controls as existing tools).</td> </tr> <tr> <td rowspan="1">❌ One compromised credential = full breach.</td> <td>✅ Breach limited to single user's permissions.</td> </tr> </tbody> </table> <p><strong><em>Real-world example</em></strong>:</p> <ul> <li>Finance analyst asks about revenue → Sees all financial data (authorized)</li> <li>Marketing analyst asks same question → Sees only marketing budget (restricted)</li> <li>Same AI tool, different permissions → Security enforced automatically</li> </ul> <p><strong><em>Technical term</em></strong>: This is called “identity propagation” or “on-behalf-of flow” in enterprise security.</p> <h4 id="layer-6-data-processing--full-context-not-fragments">Layer 6: Data processing — Full context, not fragments</h4> <p><strong><em>The old way (Retrieval Augmented Generation (RAG))</em></strong>:</p> <ol> <li>User asks a question.</li> <li>System searches for relevant document chunks.</li> <li>System sends top 5 chunks to AI.</li> <li>AI answers based on fragments.</li> </ol> <p><strong>Problem</strong>: AI might miss context from other parts of the document.</p> <p><strong><em>The BriX way (Full context)</em></strong>:</p> <ol> <li>User uploads a document.</li> <li>BriX feeds the entire document into the AI’s context window.</li> <li>AI reads and understands the full document.</li> <li>AI answers with complete context.</li> </ol> <p><strong>Why this works now</strong>: Modern AI models (Claude, GPT-4) have massive context windows (100K+ tokens). They can process entire documents, not just snippets—resulting in more accurate answers and fewer hallucinations.</p> <p><strong><em>Example</em></strong>:</p> <p>Question: “What’s our refund policy for international orders?”</p> <ul> <li>RAG approach: Finds 3 snippets about refunds → Might miss international-specific rules</li> <li>BriX approach: Reads entire policy document → Finds exact international refund section</li> </ul> <h4 id="architecture-summary-why-this-design-works">Architecture summary: Why this design works</h4> <table class="table"> <thead> <tr> <th>Design choice</th> <th>Benefit</th> <th>User impact</th> </tr> </thead> <tbody> <tr> <td rowspan="1">Streaming architecture</td> <td>Real-time feedback</td> <td>Feels fast and responsive</td> </tr> <tr> <td rowspan="1">Modular Bricks</td> <td>Specialized agents</td> <td>Fewer errors, more reliable</td> </tr> <tr> <td rowspan="1">Hot/Cold memory</td> <td>Speed + durability</td> <td>Fast responses + full history</td> </tr> <tr> <td rowspan="1">Identity propagation</td> <td>User-level security</td> <td>Only see authorized data</td> </tr> <tr> <td rowspan="1">Full context processing</td> <td>Complete understanding</td> <td>More accurate answers</td> </tr> </tbody> </table> <p><strong>The result</strong>: An AI platform that feels as fast as ChatGPT but with enterprise-grade security and reliability.</p> <h3 id="what-using-brix-actually-feels-like">What using BriX actually feels like</h3> <p>All the technical architecture is invisible to end users. Here’s what they actually see and experience.</p> <h4 id="login-one-click-no-new-passwords">Login: One click, no new passwords</h4> <p><strong><em>What users see</em></strong>:</p> <ul> <li>Visit BriX URL</li> <li>Click “Log in with SSO” (uses your existing company login)</li> <li>Redirects to familiar authentication screen</li> <li>Logged in automatically</li> </ul> <p><strong><em>What users DON’T see</em></strong>:</p> <ul> <li>No new account creation</li> <li>No password to remember</li> <li>No security questionnaire</li> <li>BriX inherits your existing permissions automatically</li> </ul> <p>Why this matters: Zero onboarding friction. If you can access your email, you can use BriX.</p> <h4 id="the-app-library-your-companys-ai-tools">The app library: Your company’s AI tools</h4> <p><strong><em>What users see</em></strong>: Company’s internal “App Store” for AI tools.</p> <ul> <li>Each tool is pre-configured and vetted</li> <li>Click to launch (no installation)</li> <li>Tools are tailored to company’s data and processes</li> </ul> <h4 id="using-a-tool-chatgpt-style-interface">Using a Tool: ChatGPT-style interface</h4> <p><strong><em>What users see</em></strong>: See the AI “thinking” and “querying”—no black box waiting. Builds trust (“I can see it’s actually checking the data”).</p> <p><strong><em>Source citations</em></strong>: Every answer includes a data source. Click to view original data. No “trust me” answers.</p> <p><strong><em>Conversational follow-ups</em></strong>: “Why did it increase?” | “Compare to Malaysia” | “Show me a chart”</p> <p>BriX remembers the context.</p> <h4 id="data-upload-drag-drop-analyze">Data upload: Drag, drop, analyze</h4> <p><strong><em>What users have</em></strong>:</p> <ul> <li>Files are processed securely (encrypted).</li> <li>AI reads the full content.</li> <li>Users can ask questions about the files.</li> <li>Files are only visible to the uploader (privacy).</li> </ul> <h4 id="trustworthy-answers-certified-data-not-hallucinations">Trustworthy answers: Certified data, not hallucinations</h4> <p>The problem BriX solves:</p> <table class="table"> <thead> <tr> <th>ChatGPT/Generic AI</th> <th>BriX</th> </tr> </thead> <tbody> <tr> <td rowspan="1">❌ Makes up data ("hallucinations")</td> <td>✅ Only uses your company's real data</td> </tr> <tr> <td rowspan="1">❌ No source citations</td> <td>✅ Every answer cites the source</td> </tr> <tr> <td rowspan="1">❌ Can't access internal data</td> <td>✅ Connects to your data lakes, metrics, docs</td> </tr> <tr> <td rowspan="1">❌ Same answer for everyone</td> <td>✅ Respects your permissions (you only see your data)</td> </tr> </tbody> </table> <p>Why users trust it:</p> <ul> <li>✅ Specific number (not vague)</li> <li>✅ Source cited (can verify)</li> <li>✅ Certified data (governance approved)</li> <li>✅ Timestamp (know it’s current)</li> <li>✅ Can export/verify (transparency)</li> </ul> <h3 id="the-impact-what-brix-actually-changes">The impact: What BriX actually changes</h3> <p>BriX shifts how organizations build AI tools. Here’s what that looks like in practice.</p> <h4 id="from-months-to-days">From months to days</h4> <table class="table"> <thead> <tr> <th>Traditional path</th> <th>BriX path</th> </tr> </thead> <tbody> <tr> <td rowspan="1">1. Domain expert has idea.</td> <td>1. Domain expert has idea</td> </tr> <tr> <td rowspan="1">2. Submits request to engineering.</td> <td>2. Configures the idea in BriX.</td> </tr> <tr> <td rowspan="1">3. Waits in backlog (weeks to months).</td> <td>3. Tests with small group.</td> </tr> <tr> <td rowspan="1">4. Engineering rebuilds it "properly".</td> <td>4. Deploys to production.</td> </tr> <tr> <td rowspan="1">5. Tool finally launches.</td> <td>5. Shares with team.</td> </tr> </tbody> </table> <p><strong><em>What changes</em></strong>:</p> <ul> <li>⚡ Speed (hours instead of months)</li> <li>👤 Ownership (domain experts maintain their tools)</li> <li>🔄 Iteration (refine based on feedback immediately)</li> <li>✅ Success rate (ideas get tested instead of dying in backlog)</li> </ul> <h4 id="true-democratization">True democratization</h4> <p><strong><em>Who builds tools with BriX</em></strong>:</p> <p>The shift isn’t just engineers anymore. We’re seeing:</p> <ul> <li>Product managers building feature analysis tools.</li> <li>Data analysts creating custom dashboards.</li> <li>Marketing ops building campaign trackers.</li> <li>Sales ops creating pipeline monitors.</li> <li>HR analytics building retention tools.</li> </ul> <p><strong><em>What this means</em></strong>:</p> <p>Domain expertise stays with domain experts (no translation loss). Engineering focuses on platforms (not individual tool requests). Innovation happens at business speed (not constrained by engineering capacity).</p> <p><strong><em>The reality check</em></strong>:</p> <p>Not every domain expert will build tools (and that’s fine). Some tools still need engineering (complex integrations, custom logic). But the bottleneck shifts from “engineering capacity” to “good ideas.”</p> <h4 id="flexibility-without-fragility">Flexibility without fragility</h4> <p>What you can change without rewriting code:</p> <p><strong><em>Swap AI models</em></strong>:</p> <ul> <li>Dropdown menu selection (GPT-5, Claude, Gemini)</li> <li>Different teams can setup different models for their BriX</li> <li>Can test new models without rebuilding tools</li> </ul> <p><strong><em>Add data sources</em></strong>:</p> <ul> <li>New MCP connector (one-time setup)</li> <li>All existing tools can access the new source</li> <li>No need to update individual tools</li> </ul> <p><strong><em>Update behavior globally</em></strong>:</p> <ul> <li>Change system prompt in one place</li> <li>All instances follow new rules immediately</li> <li>Useful for policy updates, compliance changes</li> </ul> <p><strong>Real example</strong>: When a company needs to update data access policies:</p> <ul> <li>Traditional approach: Update each tool individually (days/weeks)</li> <li>BriX approach: Update system prompt once (minutes)</li> </ul> <h4 id="security-that-enables-not-blocks">Security that enables (Not blocks)</h4> <p><strong><em>The traditional trade-off</em></strong>:</p> <ul> <li>Secure tools = slow approval, limited functionality</li> <li>Fast tools = security nightmares, compliance issues</li> </ul> <p>BriX’s approach: Security is built into the platform, not added per tool.</p> <p><strong><em>What’s automatic</em></strong>:</p> <ul> <li>SSO authentication (no passwords to manage)</li> <li>Identity propagation (users see only their authorized data)</li> <li>Audit logging (every query tracked)</li> </ul> <p><strong><em>What this changes</em></strong>:</p> <ul> <li>Security team reviews the platform once (not every tool)</li> <li>Builders don’t need to become security experts</li> <li>Compliance is automatic (audit trails, access controls)</li> <li>Tools can move fast without sacrificing governance</li> </ul> <p><strong><em>Real impact</em></strong>: Security teams that previously rejected most AI proposals can pre-approve BriX. Then tools built on BriX inherit those security controls automatically.</p> <p><strong><em>BriX will</em></strong>:</p> <ul> <li>Provide infrastructure for rapid AI tool deployment.</li> <li>Make it easier for domain experts to productionize ideas.</li> <li>Centralize security and governance.</li> <li>Reduce (not eliminate) the engineering bottleneck.</li> <li>Give you a path from prototype to production.</li> </ul> <h4 id="the-real-impact">The real impact</h4> <p>The biggest change isn’t technical. It’s organizational.</p> <p>BriX changes the conversation from:</p> <p><strong>“Can engineering build this for us?”</strong></p> <p>to:</p> <p><strong>“Let me try building this and see if it works”</strong></p> <p>That shift—from asking permission to testing ideas—is the real impact. Some ideas will fail. That’s fine. The cost of testing is now low enough that failure is acceptable.</p> <p>The ideas that succeed can scale immediately. That’s what matters.</p> <h2 id="adoption-from-zero-to-production-reality">Adoption: From zero to production reality</h2> <p>This isn’t theoretical. Real teams are using BriX right now:</p> <ul> <li><strong>The Universal Playground</strong> - Data analysts and product managers drop in to run quick analyses or ask questions—no setup, no credentials to configure. Just connect and go. It’s become the default “let me check something” tool.</li> <li><strong>Country Intelligence Assistant</strong> - Country Analytics built a specialized assistant that answers country-specific questions—market data, regulations, operational metrics. It’s now the go-to source for regional teams making local decisions.</li> <li><strong>Medallion Architecture Validator</strong> - A data engineer created a tool that validates table compliance with medallion architecture standards. What used to take manual reviews now happens instantly. Teams query it before deployments to catch issues early.</li> <li><strong>Conversion Funnel Analyzer</strong> - Product analyst built an assistant that tracks user conversion funnels step-by-step in a custom UI. Marketing and product teams use it daily to understand drop-off points without writing SQL.</li> </ul> <h2 id="learningsconclusion">Learnings/conclusion</h2> <p><strong>The promise</strong>: Anyone can build AI tools. <strong>The reality</strong>: Anyone can build prototypes, but production requires engineering expertise most people don’t have.</p> <p><strong>BriX bridges that gap</strong>.</p> <h3 id="what-brix-does-1">What BriX does</h3> <p><strong>For domain experts</strong>: Build and own tools without becoming DevOps experts. Iterate in hours, not months. <strong>For engineering</strong>: Stop being the bottleneck. Secure the platform once, not every tool. <strong>For the organization</strong>: Test more ideas. Scale what works. Automatic security and compliance.</p> <h3 id="why-brix-works-three-design-principles">Why BriX works: Three design principles</h3> <p>Building BriX taught us that successful enterprise AI platforms require:</p> <p><strong>Specialization over generalization</strong> Users prefer 5 focused tools over 1 unpredictable tool. That’s why BriX uses modular “Bricks”—each specialized for specific tasks (data analysis, trend detection, document search). Narrow scope = better reliability.</p> <p><strong>Enablement over control</strong> Deployment slop isn’t a problem to eliminate—it’s evidence of demand. Don’t kill experimentation; provide the path to production. BriX lets teams experiment locally, then offers the infrastructure to scale what works.</p> <p><strong>Reliability over features</strong> Users forgive missing features. They don’t forgive unreliability. One slow response or wrong answer = they never come back. That’s why BriX prioritizes real-time streaming, certified data sources, and source citations over adding more capabilities.</p> <p><strong>The result</strong>: A platform that feels as fast as ChatGPT but with enterprise-grade security and governance.</p> <h3 id="configure-once-analyze-everywhere-act-fast">Configure once. Analyze everywhere. Act fast.</h3> <p>BriX makes AI tool deployment a configuration problem, not an engineering problem.</p> <p>Your domain experts have the ideas. BriX gives them the path to production.</p> <h2 id="whats-next">What’s next</h2> <p>BriX solves deployment, but we’re not stopping there.</p> <h3 id="more-data-sources">More data sources</h3> <p>We’re expanding the MCP library. If our company uses it, BriX should connect to it—securely and without custom engineering work.</p> <h3 id="bring-your-own-code">Bring your own code</h3> <p>For technical builders who want custom logic without DevOps headaches, we’re launching a mono repo setup:</p> <ul> <li>App owners own: Their code and business logic</li> <li>BriX owns: Platform, security, scaling, maintenance</li> </ul> <h3 id="more-brix">More BriX</h3> <p>Onboarding more BriX for different tech and non-tech personas.</p> <h2 id="join-us">Join us</h2> <p>Grab is a leading superapp in Southeast Asia, operating across the deliveries, mobility and digital financial services sectors. Serving over 800 cities in eight Southeast Asian countries, Grab enables millions of people everyday to order food or groceries, send packages, hail a ride or taxi, pay for online purchases or access services such as lending and insurance, all through a single app. Grab was founded in 2012 with the mission to drive Southeast Asia forward by creating economic empowerment for everyone. Grab strives to serve a triple bottom line – we aim to simultaneously deliver financial performance for our shareholders and have a positive social impact, which includes economic empowerment for millions of people in the region, while mitigating our environmental footprint.</p> <p>Powered by technology and driven by heart, our mission is to drive Southeast Asia forward by creating economic empowerment for everyone. If this mission speaks to you, <a href="https://grb.to/gebbrix">join our team</a> today!</p> Fri, 16 Jan 2026 00:23:00 +0000 https://engineering.grab.com/brix https://engineering.grab.com/brix AI LLM Deployment Engineering Demystifying user journeys: Revolutionizing troubleshooting with auto tracking <h2 id="introduction">Introduction</h2> <p>Troubleshooting critical issues by deciphering a user’s journey on the Grab app is an extremely challenging task. With countless user journeys and multiple paths through the User Interface (UI), it’s akin to searching for a needle in a vast haystack. This challenge frequently resonates with us, the dedicated developers at Grab, as we strive to understand user behaviors, views, and interactions.</p> <h2 id="the-challenge">The challenge</h2> <p>The distinction between resolving an issue effectively versus spending hours on a wild goose chase is understanding our user journey in real-time.</p> <p>The development team initially attempted to address the issue of the incomplete user journey tracking by implementing a system where a click stream event would be sent with every user interaction. However, this approach presented significant challenges due to the sheer volume of UI components—often numbering in the hundreds—and the reliance on individual developers to correctly instrument each one.</p> <p>A common pitfall was that developers would occasionally overlook or forget to instrument certain user interactions, leading to breaks in the recorded user journey. This created a highly frustrating situation for both the development and product teams, as the integrity of the user journey data was consistently compromised. Despite continuous efforts to patch these bugs and address the omissions, the team found themselves in a perpetual state of reaction, constantly trying to catch up with newly discovered breaches rather than proactively preventing them. This reactive approach consumed valuable resources and hindered the ability to gain a complete and accurate understanding of user behavior.</p> <p>Diagnosing system failures, application bugs, or poor user experiences in complex applications becomes inefficient without real-time performance metrics and detailed session tracking. When engineering teams rely on outdated or fragmented data, they are forced to piece together issue narratives reactively, long after the issues occur. This significantly delays the Mean Time To Resolution (MTTR). Such a reactive approach leads to increased downtime, higher operational costs, customer dissatisfaction, and a waste of developers’ time, as they spend more time “hunting” for clues rather than deploying solutions or new features.</p> <h2 id="our-eureka-moment-autotrack-sdk">Our ‘Eureka’ moment: AutoTrack SDK</h2> <p>The pivotal breakthrough that provides our unique advantage was the creation of auto tracking user journeys—our “Eureka” moment. To deliver this, we developed the new Software Development Kit (SDK) called AutoTrack.</p> <p>AutoTrack is system that comprehensively records application state, UI view state, as well as user interactions - a solution that pieces together a chronicle of the user journey, from launch to interactions, as they navigate through the screens. AutoTrack SDK is built on the three core pillars:</p> <ol> <li>Application state</li> <li>User interactions</li> <li>UI screens</li> </ol> <p>Let’s delve deeper into the mechanics of how this operates.</p> <h3 id="application-state">Application state</h3> <p>Understanding the application state is fundamental to comprehending user behavior and, consequently, executing effective troubleshooting. The application state provides crucial insights into how a user interacts with the app, particularly concerning its visibility and how it was initiated. This encompasses tracking when the app moves between the background and foreground, as well as the various launch mechanisms.</p> <div class="post-image-section"><figure> <img src="/img/auto-tracking/figure-1.png" alt="" style="width:50%" /><figcaption align="middle">Figure 1. Application state user flow.</figcaption> </figure> </div> <p>Key aspects of application state that are vital to monitor include:<br /> <strong>Application lifecycle transitions:</strong></p> <ul> <li><strong>Background state:</strong> When the app is running but not actively displayed to the user (e.g., the user switches to another app, or the device is locked). Understanding how frequently and for how long an app resides in the background can inform power consumption analysis and the effectiveness of background tasks.</li> <li><strong>Foreground state:</strong> When the app is actively in use and displayed to the user. Monitoring transitions into and out of the foreground provides a real-time view of user engagement.</li> <li><strong>Inactive state:</strong> A temporary state where the app is in the foreground but not receiving events (e.g., an incoming call temporarily interrupts the app).</li> <li><strong>Suspended state:</strong> An app that is in the background and has been explicitly suspended by the operating system to free up resources.</li> <li><strong>Terminated state:</strong> When the app has been completely closed or crashed. Differentiating between intentional termination and crashes is critical for identifying stability issues.</li> </ul> <p><strong>Application launch mechanisms:</strong></p> <p>The way an app is launched significantly impacts the initial user experience and can influence subsequent interactions. Tracking these different launch types is essential for understanding user entry points and for debugging issues that might be specific to a particular launch method.</p> <ul> <li><strong>Explicit user launch:</strong> This is the most straightforward launch mechanism, where the user directly taps on the app icon from their device’s home screen or app drawer. This indicates a deliberate intent to use the app and often signifies a primary entry point for regular users.</li> <li><strong>Deeplinks:</strong> Deeplinks are URLs that, when clicked, open a specific page or section within a mobile app rather than a web page. They are powerful tools for enhancing user experience and engagement by providing direct access to relevant content.</li> <li><strong>Push notifications:</strong> Push notifications are messages sent by an app to a user’s device even when the app is not actively in use. Tapping on a push notification often launches the app and directs the user to a specific context related to the notification’s content.</li> </ul> <div class="post-image-section"><figure> <img src="/img/auto-tracking/figure-2.png" alt="" style="width:80%" /><figcaption align="middle">Figure 2. Code sample for tracking application lifecycle transition.</figcaption> </figure> </div> <h3 id="user-interactions">User interactions</h3> <p>Real-time session tracking is a crucial component in understanding user behavior and optimizing app performance. By meticulously tracking a wide array of user interactions, the system provides invaluable insights into how users navigate and engage with the app. This granular data forms the bedrock for constructing comprehensive user journeys, allowing development teams to visualise the path a user takes from their initial entry point to achieving their goals within the app.</p> <p>This deep understanding of user interactions is the most important pillar in creating accurate and insightful user journey maps. These maps, in turn, are instrumental in identifying patterns of user behavior, both positive and negative. For instance, tracking helps to identify pain points, bugs, or areas of confusion that might lead to user frustration or abandonment.</p> <div class="post-image-section"><figure> <img src="/img/auto-tracking/figure-3.png" alt="" style="width:80%" /><figcaption align="middle">Figure 3. Sample code for real-time session tracking.</figcaption> </figure> </div> <h3 id="ui-screen">UI screen</h3> <p>The system leverages lifecycle events from UIViewController (iOS), Activity (Android), and Fragments (Android) to accurately identify and track which specific screen is currently displayed to the user. This granular level of screen tracking is crucial because it significantly enriches the contextual information available to us. By understanding the precise UI that users are interacting with, we can account for the dynamic nature of our app. Different geographical regions, diverse user segments, and varying operational scenarios can lead to distinct user interfaces being presented. This capability ensures that our analysis and troubleshooting efforts are always based on the actual user experience, allowing for more precise problem identification and more effective solutions.</p> <div class="post-image-section"><figure> <img src="/img/auto-tracking/figure-4-5.png" alt="" style="width:80%" /><figcaption align="middle"> </figcaption> </figure> </div> <div class="post-image-section"><figure> <img src="/img/auto-tracking/figure-6.png" alt="" style="width:80%" /><figcaption align="middle">Figure 6. Sample code of UIViewController configuration.</figcaption> </figure> </div> <h3 id="ui-screen-data">UI screen data</h3> <p>On top of that, whenever the screen appears, we capture the screen metadata where we read the full screen hierarchy. With the Screen hierarchy JSON data at hand, we employ it to train an AI model. This model, consequently, can generate an HTML file, which mirrors the user’s screen and interaction.</p> <p>Disclaimer: information is redacted in compliance with GDPR/PDPA, personal data protection laws.</p> <div class="post-image-section"><figure> <img src="/img/auto-tracking/figure-7.png" alt="" style="width:80%" /><figcaption align="middle">Figure 7. Screen hierarchy.</figcaption> </figure> </div> <h2 id="applications-of-autotrack">Applications of AutoTrack</h2> <p><strong>Key applications of AutoTrack data:</strong></p> <ul> <li><strong>Reconstructing user journeys and reproducing elusive bugs:</strong> One of the most significant benefits of AutoTrack is its ability to meticulously record user interactions within the app. This detailed session data allows our teams to precisely recreate the user journey that led to a reported issue. For bugs that are notoriously difficult to reproduce, this capability is a game-changer, eliminating hours of manual guesswork and dramatically accelerating the identification and resolution of underlying problems.</li> <li><strong>Automated issue assignment:</strong> When an issue is reported, AutoTrack data can be leveraged to automatically assign it to the most relevant team. By analysing the context of the issue within the recorded session, including the specific features or modules involved, the system can intelligently route the problem to the engineers best equipped to address it. This automation reduces triage time, ensures issues are handled by subject matter experts, and improves overall response efficiency.</li> <li><strong>Automating UI test case generation:</strong> The rich dataset provided by AutoTrack offers a powerful foundation for automating the creation of UI test cases. By observing how users interact with the interface, we can automatically generate test scripts that mimic real-world usage patterns. This not only speeds up the testing phase but also leads to more comprehensive test coverage, identifying edge cases and user flows that might otherwise be missed by manually written tests.</li> <li><strong>Understanding analytics event triggers:</strong> AutoTrack data provides a granular view into when and why specific analytics events are triggered within the application. This allows us to validate the accuracy of our analytics instrumentation, ensure that events are firing as expected, and gain deeper insights into user behavior. By understanding the precise context surrounding event triggers, we can refine our data collection strategies and derive more meaningful insights from our analytics.</li> </ul> <h2 id="key-takeaways-and-whats-next">Key takeaways and what’s next</h2> <p>AutoTrack replaces fragile manual instrumentation with a unified, real-time view of application state, screen context, and user interactions. That end-to-end trace makes elusive bugs reproducible, routes issues to the right owners, and seeds reliable UI tests—turning guesswork into grounded evidence so teams can ship fixes faster and with greater confidence.</p> <p>Looking ahead, we are expanding AutoTrack across surfaces and deepening the context it captures—pairing sessions with network and performance signals, strengthening privacy guardrails, and integrating with automated triage and test generation. Look forward to reading more of our deep dives on auto-generated UI tests and how these journeys will power proactive quality across Grab’s app.</p> <h2 id="join-us">Join us</h2> <p>Grab is a leading superapp in Southeast Asia, operating across the deliveries, mobility and digital financial services sectors. Serving over 800 cities in eight Southeast Asian countries, Grab enables millions of people everyday to order food or groceries, send packages, hail a ride or taxi, pay for online purchases or access services such as lending and insurance, all through a single app. Grab was founded in 2012 with the mission to drive Southeast Asia forward by creating economic empowerment for everyone. Grab strives to serve a triple bottom line – we aim to simultaneously deliver financial performance for our shareholders and have a positive social impact, which includes economic empowerment for millions of people in the region, while mitigating our environmental footprint.</p> <p>Powered by technology and driven by heart, our mission is to drive Southeast Asia forward by creating economic empowerment for everyone. If this mission speaks to you, <a href="https://grb.to/gebautocheck">join our team</a> today!</p> Tue, 23 Dec 2025 00:23:00 +0000 https://engineering.grab.com/auto-track-sdk https://engineering.grab.com/auto-track-sdk Mobile iOS Android Tracking Engineering Design Product How Grab is accelerating growth with real-time personalization using Customer Data Platform scenarios <h2 id="introduction">Introduction</h2> <p>Delivering personalized user experiences in real-time is central to Grab’s strategy, but achieving this at scale poses significant engineering challenges. Grab’s Customer Data Platform (CDP) and Growth team has successfully delivered several real-time campaigns, driving significant business impact through enhanced personalization. These initiatives include high-impact use cases like immediate mall offers, timely traveler recommendations, precise ad retargeting, and proactive interventions during key user journey moments. At the core of these successes is Grab’s CDP, which rapidly deploys advanced real-time personalization via a powerful new capability called “Scenarios.”</p> <h2 id="about-grabs-cdp">About Grab’s CDP</h2> <p>Grab’s CDP is a centralized, reliable repository for user attributes, designed for freshness, governance, and reusability. Built on <a href="https://engineering.grab.com/signals-market-place">Grab’s Signal Marketplace</a> framework, the CDP streamlines data management through automation and integration, supporting seamless interactions with internal services and toolings that power marketing, experimentation, ads, Machine Learning (ML) features, and external platforms, including Facebook, Google Ads, and TikTok.</p> <p>The platform currently manages over 1,000 batch user attributes for Passengers, Drivers, and Merchants, powering diverse use cases from targeted marketing campaigns to operational decision-making across Grab’s entire ecosystem.</p> <h2 id="the-need-for-real-time-personalization">The need for real-time personalization</h2> <p>In our current CDP setup, user segments are primarily created for targeting using batch attributes that update once daily. While these batch updates provide valuable historical insights, they are not suitable for scenarios requiring real-time responsiveness. This delay prevents timely engagement with users, particularly when immediate actions can significantly enhance user experiences and conversion rates.</p> <p>For example, when travelers land at an airport, they immediately benefit from timely suggestions for rides, dining options, or local attractions. Traditional batch processing cannot deliver the agility and responsiveness required for these dynamic scenarios.</p> <p>Historically, real-time personalization at Grab relied heavily on engineering resources, which resulted in limited scalability and agility. Marketers and product teams often found themselves blocked by engineering bandwidth constraints, restricting experimentation and innovation.</p> <h2 id="problem-statement">Problem statement</h2> <p>The limitations of Grab’s existing personalization frameworks include:</p> <ul> <li> <p><strong>Batch attribute delays</strong>: Daily updates are insufficient for scenarios requiring immediate user responses.</p> </li> <li> <p><strong>Limited dynamic enrichment</strong>: Difficulties in dynamically integrating real-time events with historical user data, weakens personalization effectiveness.</p> </li> <li> <p><strong>High engineering overhead</strong>: Custom solutions require extensive resources, limiting agility and innovation.</p> </li> </ul> <p>To overcome these challenges and support Grab’s vision for comprehensive personalization – including proactive recommendations and assistance – CDP needed robust real-time capabilities.</p> <h2 id="cdp-scenarios-real-time-personalization-made-simple">CDP Scenarios: Real-time personalization made simple</h2> <p>The <strong>Scenario</strong> feature revolutionizes real-time targeting within the CDP by utilizing user-initiated events, geo-fencing, historical profile data, and on-the-fly predictions. This empowers the business to deliver easy, quick, and flexible personalization without the need for complex engineering efforts.</p> <p>Scenarios enable innovative use cases such as these:</p> <ul> <li><strong>Mall personalization</strong>: Real-time personalized offers upon arrival.</li> <li><strong>Traveler assistance</strong>: Immediate recommendations at airports or hotels.</li> <li><strong>Ad retargeting</strong>: Enhanced real-time ad targeting.</li> <li><strong>Conversion optimization</strong>: Timely intervention during user drop-off points.</li> </ul> <p>Imagine predicting a user’s intent to drop off at a mall using both real-time and historical context. For instance, when a user books a ride to a mall, factors such as destination, time, cuisine preferences, and past behavior (e.g., affluence level) can help predict whether the user’s purpose is retail therapy, grocery shopping, or dining out. This prediction accounts for elements like time of day, day of the week, and mall location. Grab’s engineering teams can leverage this predicted intent (signal) to offer personalized actions, such as GrabPay discounts for shopping or exclusive dining offers for dinner.</p> <div class="post-image-section"><figure> <img src="/img/cdp-scenario/figure-1.png" alt="" style="width:100%" /><figcaption align="middle">Figure 1. Scenario in CDP.</figcaption> </figure> </div> <h3 id="key-features">Key features</h3> <ul> <li><strong>Event-driven personalization</strong>: Real-time Scenarios triggered by Scribe events (Grab’s comprehensive event collection and tracking platform) combined with geo-fencing.</li> <li><strong>Historical context integration</strong>: Optionally enrich Scenarios using historical CDP data.</li> <li><strong>Predictive modeling</strong>: Deploy pre-trained models for instant user behavior predictions.</li> <li><strong>Self-serve graphical user interface (GUI)</strong>: Enable marketers to create complex event sequences and validate Scenarios with synthetic data processed through Flink pipelines.</li> <li><strong>Headless application programming interfaces (APIs)</strong>: Allow programmatic access and management of Scenarios.</li> </ul> <div class="post-image-section"><figure> <img src="/img/cdp-scenario/figure-2.jpg" alt="" style="width:100%" /><figcaption align="middle">Figure 2. Attributes for a scenario in CDP.</figcaption> </figure> </div> <h3 id="self-serve-scenario-creation">Self-serve Scenario creation</h3> <p>We designed an intuitive self-serve UI, embedded within the Grab app, empowering marketers to quickly define and deploy Scenarios. Users can specify event triggers, configure geo-fencing, incorporate historical user attributes, and select predictive models. Marketers can also validate Scenarios using synthetic data before deployment, ensuring accurate and realistic outcomes.</p> <p>How it works:</p> <ol> <li><strong>Select event triggers</strong>: Choose predefined events or define custom intra-session sequences via the GUI.</li> <li><strong>Configure geo-fencing</strong>: Define Scenario activation locations, like airports or malls.</li> <li><strong>Include historical attributes (optional)</strong>: Utilize batch attributes from the CDP to enrich Scenarios.</li> <li><strong>Select predictive models (optional)</strong>: Train custom classifiers or pick from pre-trained Catwalk models.</li> <li><strong>Define data sink</strong>: Choose between Amphawa (DynamoDB), Kafka, or both; potentially extendable to external destinations (e.g., Appsflyer).</li> <li>Once configured, metadata synchronizes automatically with our streaming service, and Scenarios become available for real-time consumption within an hour.</li> </ol> <h2 id="proven-impact-real-world-success">Proven impact: Real-world success</h2> <p>CDP Scenarios are already delivering measurable business results, with over 12 live production implementations. For instance, in a case study addressing Grab Unlimited subscription signup abandonment, we leveraged CDP Scenarios to increase signups by engaging users in real time within 15 minutes of them leaving the signup process.</p> <div class="post-image-section"><figure> <img src="/img/cdp-scenario/figure-4.png" alt="" style="width:90%" /><figcaption align="middle">Figure 3. Grab Unlimited sign-up journey.</figcaption> </figure> </div> <p>To enhance conversion rates, personalized real-time nudges were deployed through Scenarios. For example, users who started the signup process but failed to complete it within 15 minutes received a follow-up notification, prompting them to finalize their registration.</p> <div class="post-image-section"><figure> <img src="/img/cdp-scenario/figure-5.png" alt="" style="width:80%" /><figcaption align="middle">Figure 4. Scenario flow for Grab Unlimited registration.</figcaption> </figure> </div> <p>This scenario alone achieved more than a 3% uplift in subscriber conversions vs non-real-time acquisition campaigns, demonstrating Scenarios’ potential to significantly boost business outcomes.</p> <h2 id="technical-architecture-low-latency-high-reliability">Technical architecture: Low latency, high reliability</h2> <div class="post-image-section"><figure> <img src="/img/cdp-scenario/figure-6.jpg" alt="" style="width:90%" /><figcaption align="middle">Figure 5. High-level scenario flow. Scenarios are designed for low latency (under 15 seconds) and high reliability.</figcaption> </figure> </div> <ol> <li><strong>Event registration</strong>: Popular UI events from Scribe are whitelisted and immediately available; custom events are onboarded via the CDP web portal.</li> <li><strong>Scenario creation</strong>: Users configure Scenarios through a user-friendly GUI, defining events, historical contexts, and predictive models.</li> <li><strong>Real-time Flink processing</strong>: Incoming events trigger Scenarios, evaluating user historical data via StarRocks and performing real-time predictions using pre-trained models.</li> <li><strong>Real-time data sync</strong>: Outcomes are synced back to Kafka or Amphawa (Grab’s internal feature store built on AWS DynamoDB), enriching data for use by subsequent services.</li> <li><strong>Consumption by downstream services</strong>: Kafka streams or CDP’s Profile SDK facilitates immediate, personalized user experiences.</li> </ol> <h2 id="advancing-the-future-of-real-time-personalization">Advancing the future of real-time personalization</h2> <p>As we continue to innovate, we are focused on enhancing the capabilities of CDP Scenarios to support more complex and scalable personalization use cases. Here are some key areas of improvement we are exploring:</p> <ul> <li> <p><strong>Optimized Scenario sharding for scalable processing</strong>: To accommodate the growing number of use cases, we plan to scale and orchestrate our Flink pipeline fleet in a headless manner. This approach will improve system stability and enable seamless management of complex Scenarios across the pipeline.</p> </li> <li> <p><strong>Enhanced signal distribution across multiple destinations</strong>: Currently, Scenario outputs are limited to a single topic or sink. To address the increasing diversity of use cases, we aim to expand signal distribution, allowing downstream consumers to access Scenario outcomes through multiple scalable and reliable channels.</p> </li> <li> <p><strong>Advanced scheduling and delayed triggering</strong>: While real-time computation of Scenario signals is effective, certain use cases require delayed activation for maximum impact. We are exploring ways to compute signals instantly but trigger actions at scheduled times, such as sending a push notification for booking a return Grab ride based on the average wait time at the drop-off location.</p> </li> </ul> <h2 id="conclusion-revolutionizing-real-time-personalization">Conclusion: Revolutionizing real-time personalization</h2> <p>The launch of CDP Scenarios represents a significant milestone for Grab, paving the way for scalable, efficient, and user-friendly real-time personalization. Initial successes have demonstrated its immense potential, delivering notable improvements in user engagement and conversion rates. Looking ahead, we are committed to continuously advancing Scenarios by expanding its features, integrations, and applications to further elevate user experiences across the Grab ecosystem.</p> <h2 id="join-us">Join us</h2> <p>Grab is a leading superapp in Southeast Asia, operating across the deliveries, mobility and digital financial services sectors. Serving over 800 cities in eight Southeast Asian countries, Grab enables millions of people everyday to order food or groceries, send packages, hail a ride or taxi, pay for online purchases or access services such as lending and insurance, all through a single app. Grab was founded in 2012 with the mission to drive Southeast Asia forward by creating economic empowerment for everyone. Grab strives to serve a triple bottom line – we aim to simultaneously deliver financial performance for our shareholders and have a positive social impact, which includes economic empowerment for millions of people in the region, while mitigating our environmental footprint.</p> <p>Powered by technology and driven by heart, our mission is to drive Southeast Asia forward by creating economic empowerment for everyone. If this mission speaks to you, <a href="https://grb.to/gebdef1">join our team</a> today!</p> Thu, 18 Dec 2025 00:23:00 +0000 https://engineering.grab.com/cdp-scenarios https://engineering.grab.com/cdp-scenarios Database FlinkSQL Engineering A Decade of Defense: Celebrating Grab's 10th Year Bug Bounty Program <h2 id="introduction">Introduction</h2> <p>Ten years ago, we launched our bug bounty program in partnership with <a href="https://www.hackerone.com/blog">HackerOne</a>. Beyond a security initiative, it represented an open invitation to collaborative development. As pioneers in Southeast Asia, we began the program with 23 initial researchers, and it has since evolved into a global community of security researchers.</p> <p>The strategic structure and scope of our Bug Bounty Program, combined with our continuous innovation and experimentation, have successfully captured the attention of the global security research community. Over the past decade, we have partnered with more than 850 active security researchers from HackerOne’s community of over 2 million cybersecurity professionals worldwide. These dedicated researchers work alongside us across borders and time zones, forming a collaborative defense network that helps protect over 187 million users throughout Southeast Asia. Their ongoing participation demonstrates both the maturity of our program and the trust we’ve built within the security research community.</p> <p>This milestone reflects the strength of shared purpose and our sustained partnership with the HackerOne platform. It demonstrates the value of human connection and the collective understanding that security is stronger through collaboration. Here’s to a decade of partnership and to many more years of building a safer future, one collaboration at a time!</p> <div class="post-image-section"><figure> <img src="/img/decade-of-defense/figure-1.jpg" alt="" style="width:70%" /><figcaption align="middle">Figure 1. Ten years of achievements with our HackerOne partnership.</figcaption> </figure> </div> <h3 id="evolution-and-growth-adapting-to-a-dynamic-threat-landscape">Evolution and growth: Adapting to a dynamic threat landscape</h3> <p>Over the past ten years, our program has consistently adapted to the dynamic threat landscape and integrated invaluable feedback from our research community. We have grown from a private initiative to a program that consistently ranks among the top 20 worldwide and among the top 3 in Asia on HackerOne. Key milestones from our journey include:</p> <ul> <li><strong>Expanding our horizons:</strong> Our scope significantly broadened in 2023-2024, continuously adding new assets and prominently including financial services in Indonesia and AI systems. This expansion provides researchers with more avenues to contribute to Grab’s security.</li> <li><strong>Focused mobile security:</strong> We introduced a dedicated bounty table for mobile-specific issues, recognizing the unique challenges of mobile security.</li> <li><strong>Incentivizing excellence:</strong> We regularly experiment with campaigns of various types and targets, diversifying our reward methods to include both financial rewards and recognition.</li> <li><strong>Evolving vulnerability focus:</strong> We’ve observed a significant shift in the types of vulnerabilities reported over the decade, moving from foundational issues in early years to more sophisticated and emerging categories recently.</li> </ul> <div class="post-image-section"><figure> <img src="/img/decade-of-defense/figure-2.png" alt="" style="width:70%" /><figcaption align="middle">Figure 2. The journey of our bug bounty program.</figcaption> </figure> </div> <h3 id="the-global-stage-connecting-with-the-best">The global stage: Connecting with the best</h3> <p>Our program’s success is deeply rooted in its vibrant global community, which we actively foster through continuous engagement. Our strategy extends beyond the platform to major live hacking events, including the <strong>ThreatCon Live Hacking Event 2023</strong> <strong>in Nepal</strong> and <strong>DEFCON 32’s Live Recon Village 2024 in Las Vegas.</strong> These initiatives have been instrumental in connecting us with a diverse pool of new talent and strengthening relationships with researchers across different continents. By meeting hackers where they are, we’ve not only brought new expertise into our ecosystem but also demonstrated our commitment to being an accessible and collaborative partner on a global scale.</p> <p>The high participation and quality submissions from these events demonstrate the effectiveness of this approach. They’ve expanded our global security testing coverage and strengthened our standing within the worldwide cybersecurity community. Through ongoing interactions and submitted reports, we continue to see that security is a collaborative effort with no borders.</p> <h3 id="exclusive-anniversary-celebrations-global-club-campaigns">Exclusive anniversary celebrations: Global club campaigns</h3> <p>To commemorate our 10th anniversary, we launched three exclusive, invite-only campaigns with HackerOne’s regional clubs in <strong>Germany, Morocco, and India</strong>. These campaigns served as cultural exchanges, bringing fresh perspectives from outside our core Southeast Asian consumer markets. By engaging with these clubs, we expanded our researcher community and connected with security experts who understand different threat landscapes and methodologies, bringing outside perspectives to our systems.</p> <p>In August, we also ran a broader anniversary campaign that drew significant participation from the researcher community, resulting in 461 submissions. <a href="https://hackerone.com/xchopath?type=user">xchopath</a> was awarded the Best Hacker Bonus for their contributions during this campaign.</p> <p>These campaigns expanded our global security testing coverage and strengthened relationships with international researcher communities. Beyond vulnerability reports, they functioned as knowledge-sharing initiatives. We connected directly with researchers to learn from their experience and feedback, creating a continuous loop of improvement. This international collaboration also informed our global expansion security strategy by providing insights into how different regions approach digital payments and authentication.</p> <p>The anniversary campaigns allowed us to validate our security frameworks against diverse regulatory environments and advanced testing methodologies from established security markets, reinforcing our commitment to maintaining robust security standards.</p> <h3 id="voices-from-our-community">Voices from our community</h3> <p>Behind every vulnerability report is a researcher who chose to help make Grab safer. Their perspectives reveal the human side of our security evolution. These individuals are not just cybersecurity experts; they are partners in our mission to protect millions of users and ensure a safe digital environment. Here are a few testimonies from participants in our past campaigns:</p> <ul> <li> <p>“The triage was very fast despite the time difference, which I really appreciated. The triaging experience was better than other programs. The huge scope and business portal with different user roles made it especially interesting to explore.” – <a href="https://hackerone.com/artsec?type=user"><em>ArtSec</em></a> <em>[H1 Germany club campaign participant]</em></p> </li> <li> <p>“I liked that different countries have different features—this gives me more attack surface to explore. Response time was great, triage was very fast, and I appreciated Grab’s effort in providing fast responses. The scope was huge with a lot of wildcards for reconnaissance.” – <a href="https://hackerone.com/sicksec?type=user"><em>Sicksec</em></a> <em>[H1 Morocco club campaign participant]</em></p> </li> <li> <p>“More than 20 bugs were reported, and was particularly happy that bounties were being paid upon triage. The Germany team spent a lot of time on the educational part, especially for newcomers. Communication overall was very good, and the immediate response even outside working hours was really cool. SSO and authentication is my expertise and I liked that aspect of exploring the platform.” – <a href="https://hackerone.com/lauritz?type=user"><em>Lauritz</em></a> <em>[H1 Germany club campaign participant]</em></p> </li> </ul> <h3 id="the-road-ahead-our-commitment-to-a-secure-future">The road ahead: Our commitment to a secure future</h3> <p>With a strong community of security researchers across countries and a decade of collaboration, we’ve built meaningful partnerships. Every vulnerability report represents trust, and every discovery reflects dedication to our shared mission. The program demonstrates our choice to build together rather than work in isolation, to protect rather than exploit, and to collaborate rather than compete.</p> <p>While we celebrate our external community, the success of our program relies equally on our dedicated internal teams. Our cybersecurity teams form the operational foundation of this initiative. Their consistent responsiveness and researcher-focused approach have enabled vulnerability reporting to evolve into a genuine partnership, maintaining researcher trust and keeping Grab secure.</p> <p>The next ten years will bring challenges we can’t yet imagine, from emerging threats in artificial intelligence to novel cryptographic approaches in a quantum-powered world. We will face them together as a community that spans cultures, time zones, and expertise.</p> <p>Together, we’ll continue securing Southeast Asia’s digital future, one partnership, one discovery, one shared achievement at a time.</p> <h2 id="join-us">Join us</h2> <p>Grab is a leading superapp in Southeast Asia, operating across the deliveries, mobility, and digital financial services sectors. Serving over 800 cities in eight Southeast Asian countries, Grab enables millions of people every day to order food or groceries, send packages, hail a ride or taxi, pay for online purchases or access services such as lending and insurance, all through a single app. Grab was founded in 2012 with the mission to drive Southeast Asia forward by creating economic empowerment for everyone. Grab strives to serve a triple bottom line – we aim to simultaneously deliver financial performance for our shareholders and have a positive social impact, which includes economic empowerment for millions of people in the region, while mitigating our environmental footprint.</p> <p>Powered by technology and driven by heart, our mission is to drive Southeast Asia forward by creating economic empowerment for everyone. If this mission speaks to you, <a href="https://grb.to/gebdef">join our team</a> today!</p> Mon, 01 Dec 2025 00:00:10 +0000 https://engineering.grab.com/a-decade-of-defense https://engineering.grab.com/a-decade-of-defense Engineering Performance Engineering Data Real-time data quality monitoring: Kafka stream contracts with syntactic and semantic test <h2 id="introduction">Introduction</h2> <p>In today’s data-driven landscape, monitoring data quality has become a critical need for ensuring reliable and efficient data usage across domains. High-quality data is the backbone of AI innovation, driving efficiency and unlocking new opportunities. As decentralized data ownership grows, the ability to effectively monitor data quality is essential for maintaining reliability in data systems.</p> <p>Kafka streams, as a vital component of real-time data processing, play a significant role in this ecosystem. However, unreliable data within Kafka streams can lead to errors and inefficiencies for downstream users, and monitoring the quality of data within these streams has always been a challenge. This blog introduces a solution that empowers stream users to define a data contract, specifying the rules that Kafka stream data must adhere to. By leveraging this user-defined data contract, the solution performs automated real-time data quality checks, identifies problematic data as it occurs, and promptly notifies stream owners. This ensures timely action, enabling effective monitoring and management of Kafka stream data quality while supporting the broader goals of data mesh and AI-driven innovation.</p> <h2 id="problem-statement">Problem statement</h2> <p>In the past, monitoring Kafka stream data processing lacked an effective solution for data quality validation. This limitation made it challenging to identify bad data, notify users in a timely manner, and prevent the cascading impact on downstream users from further escalating.</p> <p><strong>Challenges in syntactic and semantic issue identification</strong>:</p> <ul> <li><strong>Syntactic issues</strong>: Refers to schema mismatches between producers and consumers, which can lead to deserialization errors. While schema backward compatibility can be validated upon schema evolution, there are scenarios where the actual data in the Kafka topic does not align with the defined schema. For example, this can occur when a rogue Kafka producer is not using the expected schema for a given Kafka topic. Identifying the specific fields causing these syntactic issues is a typical challenge.</li> <li><strong>Semantic issues</strong>: Refers to inconsistencies or misalignments between producers and consumers about the expected pattern or significance of each field. Unlike Kafka stream schemas, which act as a data structure contract between producers and consumers, there is no existing framework for stakeholders to define and enforce field-level semantic rules, for example, the expected length or pattern of an identifier.</li> </ul> <p><strong>Timeliness challenge in data quality monitoring</strong>: There is no real-time mechanism to automatically validate data against predefined rules, timely identify quality issues, and promptly alert stream stakeholders. Without real-time stream validation, data quality issues can sometimes persist for periods of time, impacting various online and offline downstream systems before being discovered.</p> <p><strong>Observability challenge for troubleshooting bad data</strong>: Even when problematic data is identified, stream users face difficulties in pinpointing the exact “poison data” and understanding which fields are incompatible with the schema or violate semantic rules. This lack of visibility complicates Root Cause Analysis and resolution efforts.</p> <h2 id="solution">Solution</h2> <p>Our <a href="https://engineering.grab.com/an-elegant-platform">Coban platform</a> offers a standardized data quality test and observability solution at the platform level, consisting of the following components:</p> <ul> <li><strong>Data Contract Definition</strong>: Enables Kafka stream stakeholders to define contracts that include schema agreements, semantic rules that Kafka topic data must comply with, and Kafka stream ownership details for alerting and notifications.</li> <li><strong>Automated Test Execution</strong>: Provides a long running Test Runner to automatically execute real-time tests based on the defined contract.</li> <li><strong>Real-time Data Quality Issue Identification</strong>: Detects data issues at both syntactic and semantic levels in real-time.</li> <li><strong>Alerts and Result Observability</strong>: Alerts users, simplifying observation of data quality issues via the platform.</li> </ul> <h3 id="architecture-details">Architecture details</h3> <p>The solution includes three components: <em>Data Contract Definition, Test Execution &amp; Data Quality Issue Identification, and Result Observability as shown in the architecture diagram in figure 1</em>. All mentions of “Flow” from here onwards refer to the corresponding processes illustrated in figure 1.</p> <div class="post-image-section"><figure> <img src="/img/real-time-data-quality-monitoring/coban-architecture.jpg" alt="" style="width:100%" /><figcaption align="middle">Figure 1. Real-time Kafka Stream Data Quality Monitoring Architecture diagram. </figcaption> </figure> </div> <h4 id="data-contract-definition">Data Contract Definition</h4> <p>The Coban Platform streamlines the process of defining Kafka stream data contracts, serving as a formal agreement among Kafka stream stakeholders. This includes the following components:</p> <ul> <li><strong>Kafka Stream Schema</strong>: Represents the schema used by the Kafka topic under test and helps the Test Runner to validate schema compatibility across data streams (Flow 1.1).</li> <li><strong>Kafka Stream Configuration</strong>: Encompasses essential configurations such as the endpoint and topic name, which the platform automatically populates (Flow 1.2).</li> <li><strong>Observability Metadata</strong>: Provides contact information for notifying Kafka stream stakeholders about data quality issues and includes alert configurations for monitoring (Flow 1.3).</li> <li><strong>Kafka Stream Semantic Test Rules</strong>: Empowers users to define intuitive semantic test rules at the field level. These rules include checks for string patterns, number ranges, constant values, etc. (Flow 1.5).</li> <li><strong>LLM-Based Semantic Test Rules Recommendation</strong>: Defining dozens if not hundreds of field-specific test rules can overwhelm users. To simplify this process, the Coban Platform uses LLM-based recommendations to predict semantic test rules using provided Kafka stream schemas and anonymized sample data (Flow 1.4). This feature helps users set up semantic rules efficiently, as demonstrated in the sample UI in figure 2.</li> </ul> <div class="post-image-section"><figure> <img src="/img/real-time-data-quality-monitoring/sample-ui.png" alt="" style="width:100%" /><figcaption align="middle">Figure 2. Sample UI showcasing LLM-based Kafka stream schema field-level semantic test rules. Note that the data shown is entirely fictional. </figcaption> </figure> </div> <h4 id="data-contract-transformation">Data Contract Transformation</h4> <p>Once defined, the Coban Platform’s transformation engine converts the data contract into configurations that the Test Runner can interpret (Flow 2.1). This transformation process includes:</p> <ul> <li><strong>Kafka Stream Schema</strong>: Translates the schema defined in the data contract into a schema reference that the Test Runner can parse.</li> <li><strong>Kafka Stream Configuration</strong>: Sets up the Kafka stream as a source for the Test Runner.</li> <li><strong>Observability metadata</strong>: Sets contact information as configurations of the Test Runner.</li> <li><strong>Kafka Stream Semantic Test Rules</strong>: Transforms human-readable semantic test rules into an inverse SQL query to capture the data that violates the defined rules.</li> </ul> <div class="post-image-section"><figure> <img src="/img/real-time-data-quality-monitoring/semantic-test-rules.jpg" alt="" style="width:100%" /><figcaption align="middle">Figure 3. Illustration of semantic test rules being converted from human-readable formats into inverse SQL queries. </figcaption> </figure> </div> <h3 id="test-execution--data-quality-issue-identification">Test Execution &amp; Data Quality Issue Identification</h3> <p>Once the Test Configuration Transformation Engine generates the Test Runner configuration (Flow 2.1), the platform automatically deploys the Test Runner.</p> <h4 id="test-runner">Test Runner</h4> <p>The Test Runner utilises FlinkSQL as the compute engine to execute the tests. FlinkSQL was selected for its flexibility in defining test rules as straightforward SQL statements, enabling our platform to efficiently convert data contracts into enforceable rules.</p> <h4 id="test-execution-workflow-and-problematic-data-identification">Test Execution Workflow And Problematic Data Identification</h4> <p>FlinkSQL consumes data from the Kafka topic under test (Flow 2.2) using its own consumer group, ensuring it doesn’t impact other consumers. It runs the inverse SQL query (Flow 2.3) to identify any data that violates the semantic rules or that is syntactically incorrect in the first place. Test Runner captures such data, packages it into a data quality issue event enriched with a test summary, the total count of bad records, and sample bad data, and publishes it to a dedicated Kafka topic (Flow 3.2). Additionally, the platform sinks all such data quality events to an AWS S3 bucket (Flow 3.1) to enable deeper observability and analysis.</p> <h3 id="result-observability">Result Observability</h3> <p>Grab’s in-house data quality observability platform, Genchi, consumes problematic data captured by the Test Runner (Flow 3.3).</p> <h4 id="alerting">Alerting</h4> <p>Genchi sends Slack notifications (Flow 3.5) to stream owners specified in the data contract observability metadata. These notifications include detailed information about stream issues, such as links to sample data in Coban UI, observed windows, counts of bad records, and other relevant details.</p> <div class="post-image-section"><figure> <img src="/img/real-time-data-quality-monitoring/slack-notification.png" alt="" style="width:80%" /><figcaption align="middle">Figure 4. Sample Slack notifications </figcaption> </figure> </div> <h4 id="observability">Observability</h4> <p>Users can access the Coban UI (Flow 3.4), displaying Kafka stream test rules and sample bad records, highlighting fields and values that violate rules.</p> <div class="post-image-section"><figure> <img src="/img/real-time-data-quality-monitoring/sample-test-result.jpg" alt="" style="width:100%" /><figcaption align="middle">Figure 5. In this Sample Test Result, the highlighted fields indicate violations of the semantic test rules. </figcaption> </figure> </div> <h3 id="impact">Impact</h3> <p>Since its deployment earlier this year, the solution has enabled Kafka stream users to define contracts with syntactic and semantic rules, automate test execution, and alert users when problematic data is detected, prompting timely action. It has been actively monitoring data quality across 100+ critical Kafka topics. The solution offers the capability to immediately identify and halt the propagation of invalid data across multiple streams.</p> <h2 id="conclusion">Conclusion</h2> <p>We implemented and rolled out a solution to assist Grab engineers in effectively monitoring data quality in their Kafka streams. This solution empowers them to establish syntactic and semantic tests for their data. Our platform’s automatic testing feature enables real-time tracking of data quality, with instant alerts for any discrepancies. Additionally, we provide detailed visibility into test results, facilitating the easy identification of specific data fields that violate the rules. This accelerates the process of diagnosing and resolving issues, allowing users to swiftly address production data challenges.</p> <h2 id="whats-next">What’s next</h2> <p>While our current solution emphasizes monitoring the quality of Kafka streaming data, further exploration will focus on tracing producers to pinpoint the origin of problematic data, as well as enabling more advanced semantic tests such as cross-field validations. Additionally, we aim to expand monitoring capabilities to cover broader aspects like data completeness and freshness, and integrate with <a href="https://www.gable.ai/">Gable AI</a> to detect Data Transfer Object (DTO) changes and semantic regressions in Go producers upon committing code to the Git repository. These enhancements will pave the way for a more robust, multidimensional data quality testing solution across a wider range.</p> <h2 id="references">References</h2> <p><a href="https://www.oreilly.com/library/view/driving-data-quality/9781837635009/">Driving Data Quality with Data Contracts: A Comprehensive Guide to Building Reliable, Trusted, and Effective Data Platforms</a> by <a href="https://www.oreilly.com/search?q=author:%22Andrew%20Jones%22">Andrew Jones</a></p> <h2 id="join-us">Join us</h2> <p>Grab is a leading superapp in Southeast Asia, operating across the deliveries, mobility and digital financial services sectors. Serving over 800 cities in eight Southeast Asian countries, Grab enables millions of people everyday to order food or groceries, send packages, hail a ride or taxi, pay for online purchases or access services such as lending and insurance, all through a single app. Grab was founded in 2012 with the mission to drive Southeast Asia forward by creating economic empowerment for everyone. Grab strives to serve a triple bottom line – we aim to simultaneously deliver financial performance for our shareholders and have a positive social impact, which includes economic empowerment for millions of people in the region, while mitigating our environmental footprint.</p> <p>Powered by technology and driven by heart, our mission is to drive Southeast Asia forward by creating economic empowerment for everyone. If this mission speaks to you, <a href="https://grb.to/gebdatam">join our team</a> today!</p> Wed, 26 Nov 2025 00:00:10 +0000 https://engineering.grab.com/real-time-data-quality-monitoring https://engineering.grab.com/real-time-data-quality-monitoring Engineering Kafka Performance Data Science Data processing Real-time streaming Engineering Data SpellVault’s evolution: Beyond LLM apps, towards the agentic future <h2 id="introduction">Introduction</h2> <p>At Grab, innovation isn’t just about building new features; it’s about evolving our platforms to meet the changing needs of our users and the broader technological landscape. <a href="https://www.grab.com/sg/inside-grab/stories/ai-llm-productivity-tool-apps-coding/">SpellVault</a>, our internal AI platform, exemplifies this philosophy. When SpellVault was first launched, our vision was straightforward: empower everyone at Grab to effortlessly build and manage AI-powered apps without the need for coding. Built on the principles of Retrieval-Augmented Generation (RAG) and enhanced by plugin support, SpellVault rapidly evolved into a powerful productivity engine for the organization, enabling the creation of thousands of apps that drive automation, foster experimentation, and support production use cases.</p> <p>As the AI landscape has evolved, SpellVault has grown alongside it. Initially launched as a straightforward no-code app builder for Large Language Models (LLMs), it has now evolved into a cutting-edge platform that embraces the agentic future—a future where AI goes beyond generating responses to reasoning, acting, and dynamically adapting through the use of tools and contextual understanding.</p> <p>This article outlines SpellVault’s journey towards an agentic future and how we empower users to build AI Agents that are smarter, more adaptable, and ready for the future.</p> <h2 id="a-no-code-platform-for-building-llm-apps">A no-code platform for building LLM apps</h2> <p>SpellVault was founded with a clear mission: to democratize access to AI for everyone at Grab, regardless of their technical expertise. Initially launched as a no-code LLM app builder, the platform was built on a foundation of RAG pipelines and basic plugin support.</p> <p>Early on, we recognized that the true potential of AI apps extends beyond the capabilities of language models alone. Their real value lies in the ability to seamlessly interact with external systems and diverse data sources. This insight drove our commitment to minimizing barriers and ensuring users could access data from various sources with ease. From the very beginning, we centered our efforts on three key focus areas:</p> <h4 id="comprehensive-rag-solution-with-useful-integrations">Comprehensive RAG solution with useful integrations</h4> <p>From the start, the SpellVault team prioritized enabling users to enhance their LLM apps with data through RAG. Rather than solely relying on the LLM’s internal information, we wanted the apps to ground their responses in up-to-date, contextually relevant, and factual information. SpellVault has built-in integrations with knowledge sources such as Wikis, Google Docs, as well as plain text and PDF uploads. These capabilities empower users to build assistants that reference relevant knowledge and provide more accurate, verifiable answers.</p> <h4 id="plugins-to-fetch-information-on-demand">Plugins to fetch information on demand</h4> <p>To move beyond static knowledge retrieval, we needed a way for apps to act dynamically. This was made possible through SpellVault plugins—modular components that allow apps to interact with internal systems (e.g. service dashboards, incident trackers) and external APIs (e.g. search engines, weather data). Rather than being confined to their initial prompt and data, these plugins can fetch fresh information at runtime. From the available plugin types, users can create their own instances of plugins with custom settings, enabling highly specialized functionality tailored to their specific workflows. For instance, with SpellVault’s HTTP plugin, users can define custom endpoints and credentials, enabling their AI apps to make tailored HTTP calls during runtime. These custom plugins have become the backbone of many of our most impactful apps, empowering teams to seamlessly integrate SpellVault with their existing systems and processes.</p> <div class="post-image-section"><figure> <img src="/img/spellvault-img/image-1.png" alt="" style="width:70%" /><figcaption align="middle">Figure 1. SpellVault’s early architecture.</figcaption> </figure> </div> <h4 id="making-spellvault-accessible-via-common-interfaces-web-slack-api">Making SpellVault accessible via common interfaces: Web, Slack, API</h4> <p>One of our primary goals was to make AI seamlessly accessible and useful within the tools users already use—whether it’s a browser or Slack. With SpellVault, users can make their AI apps in minutes and start using them via browser or Slack messaging immediately and intuitively, without requiring any additional setup. We also exposed APIs that enabled other internal services to integrate with SpellVault apps for a variety of use cases. This multi-channel approach ensured that SpellVault wasn’t just a standalone sandbox but a platform woven into existing tools and processes.</p> <p>Users quickly adopted the platform, creating thousands of apps for internal productivity gains, automation, and even production use cases. The platform’s success validated our hypothesis that there was significant demand for democratized AI tools within the organization.</p> <div class="post-image-section"><figure> <img src="/img/spellvault-img/image-2.png" alt="" style="width:70%" /><figcaption align="middle">Figure 2. SpellVault’s web interface for LLM App configuration and chat.</figcaption> </figure> </div> <h2 id="evolution-over-time">Evolution over time</h2> <p>The AI landscape over the past few years has been defined by relentless change. New frameworks, execution paradigms, and standards have emerged in quick succession, each promising to make AI systems more powerful, more reliable, or more extensible. At Grab, we recognized that for SpellVault to stay relevant, it could not remain static. It needed to evolve in tandem with the ever-changing ecosystem, continuously incorporating valuable advancements while ensuring a seamless experience for our users.</p> <p>This philosophy of continuous adaptation has guided SpellVault’s journey. From its early days as a simple RAG-powered app builder with a few plugins, the platform grew to support an extensive number of plugin types, richer execution models, and eventually a unified approach to tools. Each step was a response both to the needs of our users and to the shifting definition of what “building with AI” meant in practice. Rather than opting for a complete overhaul, SpellVault has embraced incremental advancements, ensuring that users can seamlessly benefit from new capabilities without disruption.</p> <p>This approach to evolution has naturally positioned SpellVault to transition from a platform for LLM apps to one designed for AI agents. The following section delves into this transition in greater detail.</p> <h3 id="expanding-capabilities">Expanding capabilities</h3> <p>Over time, we introduced numerous new capabilities to SpellVault, driven both by user feedback and our commitment to innovation and staying ahead of industry trends. For instance, we extended support for different plugin types, enabling integrations with tools like Slack and Kibana, and continuously added more integrations to enhance the platform’s versatility. We implemented auto-updates for users’ Knowledge Vaults, ensuring their data remained current. With more users building with the platform, ensuring the trustworthiness of responses generated by SpellVault apps became increasingly important. We included citation capability to mitigate some of that concern. Recognizing the need for more precise answers to mathematical problems, we developed a feature that enabled LLMs to solve such problems using Python runtime. Additionally, many users requested an automated way to trigger their LLM apps, which led to the creation of a Task Scheduler feature that allows LLMs to schedule actions based on natural language user input.</p> <p>A significant milestone in SpellVault’s evolution was the introduction of “Workflow,” a drag-and-drop interface within the platform that empowered users to design deterministic workflows. These workflows enabled users to seamlessly combine various components from the SpellVault ecosystem—such as LLM calls, Python code execution, and Knowledge Vault lookups—in a predefined and structured manner. This enabled advanced use cases for many users.</p> <div class="post-image-section"><figure> <img src="/img/spellvault-img/image-3.png" alt="" style="width:70%" /><figcaption align="middle">Figure 3. Evolving tools landscape of SpellVault with increasing integrations.</figcaption> </figure> </div> <h3 id="shifting-the-execution-model">Shifting the execution model</h3> <p>As SpellVault evolved, a fundamental shift took place in the way its apps were executed internally. We transitioned from our <a href="https://python.langchain.com/docs/how_to/agent_executor/">legacy executor system</a>, which facilitated one-off information retrieval from the Knowledge Vault or user plugins, to a more advanced <a href="https://langchain-ai.github.io/langgraph/concepts/low_level/">graph based executor</a>. This empowered SpellVault’s app execution with nodes, edges, and states that supported branching, looping, and modularity. This laid the groundwork for more sophisticated agent behaviors, moving beyond the linear input-output paradigm.</p> <p>This transformed all existing SpellVault apps into ‘Reasoning and Acting’ agents, better known as <a href="https://python.langchain.com/api_reference/langchain/agents/langchain.agents.react.agent.create_react_agent.html">ReAct agents</a> - a “one size fits many” solution that significantly enhanced the capabilities of these apps. By enabling them to leverage the Knowledge Vault and plugins in a more agentic and dynamic manner, the ReAct agent framework allowed apps to perform more complex tasks while seamlessly preserving their existing functionality, ensuring no disruption to their behavior.</p> <p>In addition, the internal decoupling of the executor and prompt engineering components enabled us to design multiple execution pathways with ease. This allowed us to provide generic Deep Research capability to any SpellVault app via a simple UI checkbox, as well as sophisticated internal workflows that cater to high-ROI complex use cases like on-call alert analysis. The Deep Research capability came with SpellVault’s ability to search across internal information repositories (e.g., Slack messages, Wiki, Jira) within Grab, as well as searching online for relevant information.</p> <div class="post-image-section"><figure> <img src="/img/spellvault-img/image-4.png" alt="" style="width:70%" /><figcaption align="middle">Figure 4. SpellVault’s evolved architecture with more dynamic context gathering and advanced interaction modes.</figcaption> </figure> </div> <h3 id="towards-an-agentic-framework">Towards an agentic framework</h3> <p>Over time, several capabilities were added to SpellVault, including features like Python code execution and internal repository search. Initially, these functionalities were integrated directly into the core PromptBuilder class. For users, these features were primarily accessible through simple checkboxes in the user interface. As SpellVault gradually transitioned towards giving more agency to user-crafted apps, we recognized that these capabilities should instead be positioned as “Tools” for LLMs to use with greater autonomy, similar to how ReAct agent–backed apps have been using SpellVault’s user plugins. We also understood that this shift could bring a clearer mental model for users where they were no longer simply toggling features but creating AI agents with access to a defined set of tools. The agents could then decide when and how to use those tools intelligently to accomplish tasks, making the overall experience more natural and intuitive.</p> <p>This recognition led to the consolidation of these scattered capabilities into a unified framework called “Native Tools.” These Native Tools, along with SpellVault’s existing user plugins—rebranded as “Community Built Tools”—formed a comprehensive collection of tools that LLMs could dynamically invoke at runtime. Despite being grouped under the same umbrella, a key distinction was maintained: Native Tools required no user-specific configuration (e.g., performing internet searches), whereas Community Built Tools were custom, user-configured entities (e.g., invoking specific HTTP endpoints) created from available plugin types, often requiring credentials or other personalized settings.</p> <p>This consolidation of capabilities under a unified Tools abstraction and enabling SpellVault apps to invoke them with greater autonomy marked a pivotal milestone in the platform’s evolution. It meaningfully shifted SpellVault toward making agentic behavior more natural, discoverable, and extensible for every app.</p> <div class="post-image-section"><figure> <img src="/img/spellvault-img/image-5.png" alt="" style="width:70%" /><figcaption align="middle">Figure 5. SpellVault’s Unified Tools housing both Native Tools and Community Built Tools.</figcaption> </figure> </div> <h3 id="spellvault-as-an-mcp-service">SpellVault as an MCP service</h3> <p>As we streamlined SpellVault’s internal capabilities into a unified tools framework, we also turned our focus outward to align with industry standards. The growing adoption of the <a href="https://modelcontextprotocol.io/docs/getting-started/intro">Model Context Protocol</a> (MCP) presented an opportunity for agents and clients to seamlessly interact without requiring custom integrations. To remain at the forefront of innovation, we adapted SpellVault to function as an MCP service, enabling it to actively participate in this evolving ecosystem. This extension brought two key advancements:</p> <ul> <li> <p><strong>SpellVault apps as MCP tools</strong>: Each app created in SpellVault can now be exposed through the MCP protocol. This allows other agents or MCP-compatible clients, such as IDEs or external orchestration frameworks, to treat a SpellVault app as a callable tool. Instead of living only inside our web user interface or Slack interface, these apps become accessible building blocks that other systems can invoke dynamically.</p> </li> <li> <p><strong>RAG as an MCP tool</strong>: We extended the same idea to our Knowledge Vaults. Through MCP, external clients can search, retrieve, and even add information to Vaults. This effectively turns SpellVault’s RAG pipeline into an MCP-native service, making contextual grounding available to agents beyond SpellVault itself.</p> </li> </ul> <p>While building the SpellVault MCP Server, we also created <a href="https://github.com/grab/tinymcp">TinyMCP</a> - a lightweight open-source Python library that adds MCP capabilities to an existing FastAPI app as just another router, instead of mounting a separate app.</p> <p>By exposing both apps and RAG through MCP, we shifted SpellVault from being a self-contained platform to becoming an interoperable service provider in the agentic ecosystem. Users still benefit from the no-code simplicity inside SpellVault. However, the output of their work, apps, and knowledge, are now usable by other agents and tools outside of it.</p> <h2 id="conclusion">Conclusion</h2> <p>SpellVault’s evolution shows how a platform can adapt with the AI landscape while staying true to its original mission of making powerful technology accessible to everyone. What began as a no-code builder for LLM apps has steadily expanded into an agentic platform - one where apps can act with more intelligence, agency, and context and interact with the systems around them.</p> <p>This progress wasn’t the result of a single breakthrough, but of steady, incremental improvements that introduced new capabilities while preserving ease of use. By layering in these advancements thoughtfully but boldly, SpellVault has managed to support more sophisticated agentic behaviors without compromising its original goal of democratizing AI at Grab.</p> <h2 id="join-us">Join us</h2> <p>Grab is a leading superapp in Southeast Asia, operating across the deliveries, mobility and digital financial services sectors. Serving over 800 cities in eight Southeast Asian countries, Grab enables millions of people everyday to order food or groceries, send packages, hail a ride or taxi, pay for online purchases or access services such as lending and insurance, all through a single app. Grab was founded in 2012 with the mission to drive Southeast Asia forward by creating economic empowerment for everyone. Grab strives to serve a triple bottom line – we aim to simultaneously deliver financial performance for our shareholders and have a positive social impact, which includes economic empowerment for millions of people in the region, while mitigating our environmental footprint.</p> <p>Powered by technology and driven by heart, our mission is to drive Southeast Asia forward by creating economic empowerment for everyone. If this mission speaks to you, <a href="https://grb.to/gebspell">join our team</a> today!</p> Fri, 21 Nov 2025 00:00:10 +0000 https://engineering.grab.com/spellvault-evolution-beyond-llm https://engineering.grab.com/spellvault-evolution-beyond-llm Engineering Performance Engineering Data