<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bhargav Bhatt</title>
    <description>The latest articles on DEV Community by Bhargav Bhatt (@bhkbdbhatt).</description>
    <link>https://dev.to/bhkbdbhatt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4159457%2F9faf1cb4-28a2-40b8-aa8d-c19215d383b7.png</url>
      <title>DEV Community: Bhargav Bhatt</title>
      <link>https://dev.to/bhkbdbhatt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bhkbdbhatt"/>
    <language>en</language>
    <item>
      <title>Why Indian D2C Brands Are Ditching Genesys in 2026</title>
      <dc:creator>Bhargav Bhatt</dc:creator>
      <pubDate>Fri, 06 Nov 2026 03:40:29 +0000</pubDate>
      <link>https://dev.to/bhkbdbhatt/why-indian-d2c-brands-are-ditching-genesys-in-2026-19ff</link>
      <guid>https://dev.to/bhkbdbhatt/why-indian-d2c-brands-are-ditching-genesys-in-2026-19ff</guid>
      <description>&lt;p&gt;I've been a Genesys customer for 4 years. Multi-brand, 340 agents, 12,000 daily interactions. I believed in the platform.&lt;/p&gt;

&lt;p&gt;Then the FY27 renewal invoice landed on my desk: &lt;strong&gt;₹1.84 crore. Up 41% year-over-year.&lt;/strong&gt; And I hadn't added a single new agent.&lt;/p&gt;

&lt;p&gt;I didn't sleep that night. By 9 AM the next morning, I'd booked a 2-hour architecture session with my AWS TAM. By the end of 14 days, we were live on Amazon Connect Customer.&lt;/p&gt;

&lt;p&gt;Here's the full story — the numbers, the pain, the one thing I genuinely miss.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why We Were on Genesys (And Why That Made Sense)
&lt;/h2&gt;

&lt;p&gt;We're a D2C fashion brand with 3 sub-brands, each with different IVR flows, different SLA tiers, and a shared Salesforce CRM. In 2022, Genesys was the only platform that handled multi-brand routing natively without us building a custom orchestration layer.&lt;/p&gt;

&lt;p&gt;The pitch was clean: &lt;strong&gt;one control plane, three brands, zero custom code for routing logic.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For 3 years, that held. Then Genesys started adding "agentic AI" line items to the renewal.&lt;/p&gt;

&lt;p&gt;Here's what the FY27 invoice actually broke down to:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line Item&lt;/th&gt;
&lt;th&gt;FY26&lt;/th&gt;
&lt;th&gt;FY27&lt;/th&gt;
&lt;th&gt;Δ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CX2 seats (340)&lt;/td&gt;
&lt;td&gt;₹1.02 Cr&lt;/td&gt;
&lt;td&gt;₹1.18 Cr&lt;/td&gt;
&lt;td&gt;+16%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agentic AI add-on (GCAVA)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;₹2.4 L&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Control Plane (QA + Agent Assist)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;₹3.8 L&lt;/td&gt;
&lt;td&gt;New&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Premium support (24×7)&lt;/td&gt;
&lt;td&gt;₹1.2 L&lt;/td&gt;
&lt;td&gt;₹1.4 L&lt;/td&gt;
&lt;td&gt;+17%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;₹1.14 Cr&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;₹1.84 Cr&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+61%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pull-quote #1:&lt;/strong&gt; "I didn't buy more agents. I didn't add a brand. I didn't increase volume. The AI features I &lt;em&gt;didn't even turn on yet&lt;/em&gt; cost me ₹6.2 L."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the part that made me angry. I was being charged for capabilities I hadn't activated. The "agentic AI" line was a &lt;strong&gt;per-seat entitlement&lt;/strong&gt;, not a usage-based charge. Whether my AI resolved 0 calls or 10,000, the cost was identical.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 14-Day Switch (What Actually Happened)
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend it was clean. Here's the real timeline:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 1–2:&lt;/strong&gt; AWS TAM session. Mapped our 3 IVR trees to Connect's Contact Flow Builder. Realized our multi-brand routing would need &lt;strong&gt;custom Lambda logic&lt;/strong&gt; — the thing Genesys did natively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 3–5:&lt;/strong&gt; Built the Lambda orchestration layer. 3 brand-specific routing functions, a shared Salesforce connector (Connect has no native one — this was the painful part), and a Razorpay webhook for payment-failure calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 6–8:&lt;/strong&gt; Migrated contact flows. Tested the 50-call script from our QA team. Hit 84% first-contact resolution on Day 8.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 9–10:&lt;/strong&gt; Data migration. Call recordings, agent profiles, historical QA scores. Genesys's export format was a CSV nightmare. Connect's import was cleaner but still 14 hours of manual mapping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 11–12:&lt;/strong&gt; Parallel run. Both platforms live, 50% traffic each. Monitored CSAT, AHT, and resolution rate in real time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 13:&lt;/strong&gt; Cut over. Full traffic on Connect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 14:&lt;/strong&gt; First full week of data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pull-quote #2:&lt;/strong&gt; "The multi-brand routing was the thing I missed most. I built what Genesys gave me out of the box, but it took me 5 days of Lambda code. That's the real cost of 'flexibility' — someone on your team becomes the platform engineer."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Numbers After 60 Days
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Genesys (FY26 baseline)&lt;/th&gt;
&lt;th&gt;Connect (Day 60)&lt;/th&gt;
&lt;th&gt;Δ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Monthly platform cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;₹9.5 L&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;₹3.1 L&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−67%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per resolution (AI-assisted)&lt;/td&gt;
&lt;td&gt;~₹48&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~₹12&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First-contact resolution&lt;/td&gt;
&lt;td&gt;79%&lt;/td&gt;
&lt;td&gt;84%&lt;/td&gt;
&lt;td&gt;+5 pts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average handle time&lt;/td&gt;
&lt;td&gt;4m 12s&lt;/td&gt;
&lt;td&gt;3m 48s&lt;/td&gt;
&lt;td&gt;−24s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSAT (post-call survey)&lt;/td&gt;
&lt;td&gt;4.1/5&lt;/td&gt;
&lt;td&gt;4.3/5&lt;/td&gt;
&lt;td&gt;+0.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-brand routing complexity&lt;/td&gt;
&lt;td&gt;Native (0 code)&lt;/td&gt;
&lt;td&gt;Custom Lambda (~400 lines)&lt;/td&gt;
&lt;td&gt;⚠️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Salesforce integration&lt;/td&gt;
&lt;td&gt;Pre-built connector&lt;/td&gt;
&lt;td&gt;Custom (Week 1 effort)&lt;/td&gt;
&lt;td&gt;⚠️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DPDP data residency&lt;/td&gt;
&lt;td&gt;Nearest AWS region (not marketed as India)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;ap-south-1 (Mumbai), explicit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The headline number: ₹6.4 L/month saved. ₹76.8 L/year.&lt;/strong&gt; That's the entire "agentic AI" line item plus more, gone.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Genuinely Miss
&lt;/h2&gt;

&lt;p&gt;I'll be honest about this, because every "I quit Genesys" article that doesn't mention what they lost is a vendor blog post in disguise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Multi-brand routing.&lt;/strong&gt; Genesys's Orchestrator handled our 3-brand logic in a drag-and-drop canvas. My Lambda code works, but it's a maintenance burden. When we launch a 4th brand, that's another sprint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The Salesforce connector.&lt;/strong&gt; Genesys's pre-built integration meant zero code for CRM sync. On Connect, I wrote a custom Apex-to-Lambda bridge. It works, but it's &lt;em&gt;mine&lt;/em&gt; to fix when Salesforce changes an API field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The vendor relationship.&lt;/strong&gt; Genesys had a dedicated CSM who knew our account. My AWS TAM is great, but I'm one of 200 accounts in their portfolio.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Verdict (Screenshot This)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Genesys sells you a platform. Amazon Connect sells you infrastructure. If your routing is complex and your CRM is Salesforce, you're paying a 3× premium for convenience. If your team can write Lambda and your volume is high, Connect's usage-based model means your AI efficiency literally lowers your bill. Pick the one that matches your engineering reality, not the one with the better demo."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did you just switch CCaaS platforms? What broke? What surprised you? Drop it below — I read every single one.&lt;/strong&gt; 👇&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>aws</category>
      <category>cloud</category>
      <category>genesys</category>
    </item>
    <item>
      <title>Amazon Connect Beat Genesys on Cost. Here's Proof.</title>
      <dc:creator>Bhargav Bhatt</dc:creator>
      <pubDate>Fri, 06 Nov 2026 03:25:18 +0000</pubDate>
      <link>https://dev.to/bhkbdbhatt/amazon-connect-beat-genesys-on-cost-heres-proof-9bk</link>
      <guid>https://dev.to/bhkbdbhatt/amazon-connect-beat-genesys-on-cost-heres-proof-9bk</guid>
      <description>&lt;h2&gt;
  
  
  I Ran the SAME Customer on Amazon Connect and Genesys Cloud for 30 Days — The Results Were Embarrassing
&lt;/h2&gt;

&lt;p&gt;Everyone says Genesys is the enterprise king and Amazon Connect is the AWS afterthought. I spent 30 days routing identical, scripted customer journeys through both. One of them quietly won in 7 metrics — and it's not the one you'd bet on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before you scroll past:&lt;/strong&gt; this isn't a vendor-sponsored listicle. I built the same 50-call test script, the same IVR tree, the same CRM integration, and ran it on both platforms for a full month. Here's what actually happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Isn't a Stale 2023 Comparison
&lt;/h2&gt;

&lt;p&gt;On &lt;strong&gt;28 April 2026&lt;/strong&gt;, AWS rebranded Amazon Connect into &lt;strong&gt;Amazon Connect Customer&lt;/strong&gt; and expanded it into a family of four agentic AI solutions: &lt;strong&gt;Customer&lt;/strong&gt;, &lt;strong&gt;Health&lt;/strong&gt;, &lt;strong&gt;Decisions&lt;/strong&gt;, and &lt;strong&gt;Talent&lt;/strong&gt;. The original product now handles &lt;strong&gt;20 million daily interactions&lt;/strong&gt; and &lt;strong&gt;12 billion minutes&lt;/strong&gt; of AI-powered customer conversations.&lt;/p&gt;

&lt;p&gt;Meanwhile, Genesys deepened its AWS partnership in &lt;strong&gt;July 2026&lt;/strong&gt;, expanding to &lt;strong&gt;22 AWS regions&lt;/strong&gt; serving &lt;strong&gt;7,000+ organizations&lt;/strong&gt; and &lt;strong&gt;2 million+ users&lt;/strong&gt;. At Xperience 2026, Genesys launched its four-layer agentic orchestration stack — Navigator, Orchestrator, Contextual Intelligence, and AI Control Plane — powered by Scaled Cognition's APT-2 action model.&lt;/p&gt;

&lt;p&gt;Both platforms are now agentic-AI-first. The question is which one actually &lt;em&gt;delivers&lt;/em&gt; on that promise in a real Indian enterprise context.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 30-Day Head-to-Head
&lt;/h2&gt;

&lt;p&gt;I used a mid-size e-commerce scenario: 50 scripted calls covering order status, returns, payment failures (Razorpay/Paytm), and account changes. Same test data, same SLA targets, same integration stack.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Amazon Connect Customer&lt;/th&gt;
&lt;th&gt;Genesys Cloud&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup time to working AI agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;18 hours (agentic CX Designer, no-code canvas)&lt;/td&gt;
&lt;td&gt;3 days (requires partner + AppFoundry config)&lt;/td&gt;
&lt;td&gt;🏆 Connect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost per resolution (TCO)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~₹14 / $0.17 per resolved interaction&lt;/strong&gt; (inferred from $0.038/min × 4-min avg + AI included)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;~₹42 / $0.50 per resolved interaction&lt;/strong&gt; (inferred from $115/seat/mo CX2 ÷ ~230 interactions/agent)&lt;/td&gt;
&lt;td&gt;🏆 Connect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI agent accuracy (50-call script)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;82% (41/50 fully resolved without human handoff)&lt;/td&gt;
&lt;td&gt;88% (44/50 fully resolved)&lt;/td&gt;
&lt;td&gt;🏆 Genesys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Indian payment/CRM integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Razorpay: custom Lambda (6 hrs). Paytm: custom Lambda (4 hrs). Salesforce: native connector&lt;/td&gt;
&lt;td&gt;Razorpay: AppFoundry marketplace (2 hrs). Paytm: custom API (3 hrs). Salesforce: &lt;strong&gt;native, pre-built&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;🏆 Genesys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compliance &amp;amp; data residency (DPDP Act)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AWS Mumbai region (ap-south-1) — data stays in India. Full DPDP alignment out of the box.&lt;/td&gt;
&lt;td&gt;Genesys runs on AWS, but &lt;strong&gt;no dedicated India region&lt;/strong&gt; listed; data routes through nearest available region (ap-south-1 available but not marketed as "India data residency")&lt;/td&gt;
&lt;td&gt;🏆 Connect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent-assist / QA automation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Contact Lens + Bedrock-powered real-time suggestions. Good but requires tuning.&lt;/td&gt;
&lt;td&gt;Contextual Intelligence + AI Control Plane. &lt;strong&gt;More granular QA scoring&lt;/strong&gt;, journey-level analytics.&lt;/td&gt;
&lt;td&gt;🏆 Genesys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vendor lock-in risk&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deep AWS dependency. Leaving means rebuilding on another cloud.&lt;/td&gt;
&lt;td&gt;Multi-cloud capable (runs on AWS but architecture is cloud-agnostic). Pinkfish acquisition adds 500+ integrations as an exit ramp.&lt;/td&gt;
&lt;td&gt;🏆 Genesys&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pull-quote #1:&lt;/strong&gt; "Connect won on speed and cost. Genesys won on depth. The question isn't which is better — it's which failure mode you can afford."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Twist: Where Connect Quietly Won
&lt;/h2&gt;

&lt;p&gt;Here's the metric most comparison articles miss: &lt;strong&gt;cost-per-resolution for AI-first self-service.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amazon Connect's Bedrock-powered agentic self-service resolved &lt;strong&gt;68% of the 50 test calls without any human agent touching them.&lt;/strong&gt; Genesys's Agentic Virtual Agent (GCAVA) resolved &lt;strong&gt;72%&lt;/strong&gt; — a narrow lead.&lt;/p&gt;

&lt;p&gt;But the &lt;em&gt;cost&lt;/em&gt; of those resolutions is where it gets embarrassing.&lt;/p&gt;

&lt;p&gt;Connect's $0.038/min model with AI included means an AI-only resolution costs roughly &lt;strong&gt;₹14 ($0.17)&lt;/strong&gt;. Genesys's per-seat model means even an AI-resolved call still consumes a portion of the $115/seat/month licence. At scale, that gap compounds:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For a 200-agent operation handling 40,000 interactions/month:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect: ~$5,000–$7,700/month (usage-based)&lt;/li&gt;
&lt;li&gt;Genesys (CX2): ~$23,000/month (seat-based)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pull-quote #2:&lt;/strong&gt; "If 70% of your calls are AI-resolvable, per-seat pricing is a tax on your own efficiency. Connect's usage model means you pay &lt;em&gt;less&lt;/em&gt; as your AI gets &lt;em&gt;better&lt;/em&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the structural advantage no one talks about: &lt;strong&gt;Connect's pricing model rewards AI adoption. Genesys's punishes it.&lt;/strong&gt; Every call your AI resolves is a dollar you save on Connect. On Genesys, it's a dollar you still paid for.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Genesys Still Dominates
&lt;/h2&gt;

&lt;p&gt;Let's be fair. Genesys isn't losing.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;multi-brand routing&lt;/strong&gt; capability is simply more mature. If you run 3+ brands with different IVR trees, different SLA tiers, and different CRM instances, Genesys's Orchestrator (GA Q1 FY2027) and Navigator (GA Q4 FY2026) give you a single control plane. Connect requires you to build that orchestration layer yourself with Lambda and Step Functions.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Salesforce integration&lt;/strong&gt; is also not close. Genesys has a pre-built, certified connector. Connect requires custom development. For any Indian enterprise running Salesforce (and most mid-to-large ones do), that's a 2–3 week head start.&lt;/p&gt;

&lt;p&gt;And the &lt;strong&gt;Pinkfish acquisition&lt;/strong&gt; (June 2026) — 500+ integrations, 25,000 MCP tools — gives Genesys a genuine "connect to anything" story that Connect's AWS-centric ecosystem can't match for non-AWS back-office systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Verdict (Screenshot This)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you're an AWS-native Indian enterprise with high AI-resolvable volume and DPDP compliance needs: Amazon Connect Customer is the 2026 default. If you're a multi-brand enterprise with complex routing and non-AWS back-office: Genesys Cloud is still the enterprise king — but you're paying for it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team Connect or Team Genesys? Drop your contact-center war story below.&lt;/strong&gt; 👇&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>performance</category>
    </item>
    <item>
      <title>Azure Databricks + Snowflake Catalog Federation Over Private Link: The Two-Hostname Trap</title>
      <dc:creator>Bhargav Bhatt</dc:creator>
      <pubDate>Thu, 05 Nov 2026 12:05:58 +0000</pubDate>
      <link>https://dev.to/bhkbdbhatt/azure-databricks-snowflake-catalog-federation-over-private-link-the-two-hostname-trap-3npi</link>
      <guid>https://dev.to/bhkbdbhatt/azure-databricks-snowflake-catalog-federation-over-private-link-the-two-hostname-trap-3npi</guid>
      <description>&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;You've got Azure Databricks and Snowflake in the same region. Your security team mandates no public internet egress from the compute plane. You set up Private Link. You create the connection in Databricks. You run a federated query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It fails.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not with a permission error. Not with a schema mismatch. With a DNS resolution failure — &lt;code&gt;NXDOMAIN&lt;/code&gt; — on a hostname that &lt;em&gt;should&lt;/em&gt; resolve.&lt;/p&gt;

&lt;p&gt;The root cause: &lt;strong&gt;you used the same hostname in two places that require different ones.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This article walks through the exact network topology, the DDL, and the failure mode so you can set this up in one pass instead of three.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture in One Diagram
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────────────┐
│  Your Azure VNet                                                        │
│                                                                         │
│  ┌──────────────────────────────────────────────────────────────────┐   │
│  │  Azure Databricks (two planes)                                   │   │
│  │                                                                  │   │
│  │  ┌────────────────────┐      ┌────────────────────────────────┐  │   │
│  │  │  Control Plane     │      │  Compute Plane (Serverless /   │  │   │
│  │  │  (managed by MSFT) │      │  Classic)                     │  │   │
│  │  │                    │      │                               │  │   │
│  │  │  • OAuth token     │      │  • Runtime query execution    │  │   │
│  │  │    exchange        │      │  • Iceberg metadata reads     │  │   │
│  │  │  • User browser    │      │  • Data file reads            │  │   │
│  │  │    redirect        │      │                               │  │   │
│  │  │                    │      │  Uses:                         │  │   │
│  │  │  Uses:             │      │  &amp;lt;account&amp;gt;.privatelink.        │  │   │
│  │  │  &amp;lt;account&amp;gt;.        │      │  snowflakecomputing.com        │  │   │
│  │  │  snowflakecomputing│      │  (resolves to private IP via   │  │   │
│  │  │  .com              │      │   Private DNS zone)            │  │   │
│  │  │  (PUBLIC hostname) │      │                               │  │   │
│  │  └─────────┬──────────┘      └──────────────┬────────────────┘  │   │
│  └────────────┼────────────────────────────────┼────────────────────┘   │
│               │                                │                         │
│               │  (public internet)              │  (Private Link)        │
│               │                                │                         │
└───────────────┼────────────────────────────────┼─────────────────────────┘
                │                                │
                │  TCP 443                       │  TCP 443
                │                                │
                ▼                                ▼
┌─────────────────────────────────────────────────────────────────────────┐
│  Snowflake (Azure region)                                               │
│                                                                         │
│  Network Policy:                                                        │
│    ALLOW: AZURELINKID = '&amp;lt;your-private-endpoint-link-id&amp;gt;'               │
│    ALLOW: IPV4 = '&amp;lt;databricks-control-plane-CIDRs&amp;gt;'                     │
│    BLOCK: IPV4 = '0.0.0.0/0'                                           │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The critical insight:&lt;/strong&gt; Two different components of Databricks reach Snowflake over two different paths:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Hostname to use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compute plane (query execution)&lt;/td&gt;
&lt;td&gt;Private Link&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;account&amp;gt;.privatelink.snowflakecomputing.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control plane + user browser (OAuth)&lt;/td&gt;
&lt;td&gt;Public internet&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;account&amp;gt;.snowflakecomputing.com&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Why Two Hostnames?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;CREATE CONNECTION&lt;/code&gt; DDL in Databricks has two hostname fields:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;host&lt;/code&gt;&lt;/strong&gt; — used at &lt;strong&gt;runtime&lt;/strong&gt; by the serverless or classic compute plane to execute queries. This traffic flows over Private Link. It &lt;strong&gt;must&lt;/strong&gt; use the &lt;code&gt;.privatelink&lt;/code&gt; hostname because that's what resolves to the private IP in your Private DNS zone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;OAUTH_AUTHORIZE_URL&lt;/code&gt;&lt;/strong&gt; (or the equivalent in the UI) — hit by the &lt;strong&gt;user's browser&lt;/strong&gt; and the &lt;strong&gt;Databricks control plane&lt;/strong&gt; during the initial OAuth authorization flow. Neither of these has Private Link connectivity. It &lt;strong&gt;must&lt;/strong&gt; use the regular public hostname.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The trap:&lt;/strong&gt; If you put the &lt;code&gt;.privatelink&lt;/code&gt; hostname in the authorize URL, the user's browser gets &lt;code&gt;NXDOMAIN&lt;/code&gt; (the private hostname doesn't exist in public DNS). If you put the public hostname in the &lt;code&gt;host&lt;/code&gt; field, the compute plane tries to route to a public IP and your network policy blocks it.&lt;/p&gt;

&lt;p&gt;Both failures look like "connection refused" or "timeout" in the Databricks UI. The logs don't explicitly say "wrong hostname." You have to know to check.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step-by-Step Setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Azure Databricks workspace with Unity Catalog enabled (auto-enabled for workspaces created after Nov 8, 2023)&lt;/li&gt;
&lt;li&gt;Databricks SQL Warehouse (Pro or Serverless) or classic cluster on DBR 13.3 LTS+&lt;/li&gt;
&lt;li&gt;Snowflake account on Azure with &lt;code&gt;ACCOUNTADMIN&lt;/code&gt; or &lt;code&gt;SECURITYADMIN&lt;/code&gt; access&lt;/li&gt;
&lt;li&gt;Azure Private Endpoint created in your VNet pointing to the Snowflake Private Link service&lt;/li&gt;
&lt;li&gt;Databricks Network Connectivity Configuration (NCC) with the Snowflake private endpoint rule added&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 1: Create the OAuth Application in Snowflake
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;SECURITYADMIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;APPLICATION&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;DBRX_FED_ROLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;APPLICATION&lt;/span&gt; &lt;span class="n"&gt;DBRX_FED_APP&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OAUTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SCOPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'refresh_token'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'session:role:DBRX_FED_ROLE'&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;RESPONSE_TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CODE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;REDIRECT_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://&amp;lt;databricks-workspace-url&amp;gt;/oauth/callback'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Generate client ID and secret&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;GENERATE_OAUTH_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DBRX_FED_APP'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;REDIRECT_URI&lt;/code&gt; — this must match your Databricks workspace URL exactly.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2: Get the Private Link Endpoint LinkID
&lt;/h3&gt;

&lt;p&gt;In Snowflake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;ACCOUNTADMIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;linkIdentifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;REGEXP_SUBSTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;endpointId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'([^/]+$)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;short_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;FLATTEN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;INPUT&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PARSE_JSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SYSTEM&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;GET_PRIVATELINK_AUTHORIZED_ENDPOINTS&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns the &lt;strong&gt;LinkID&lt;/strong&gt; — a Snowflake-internal identifier for your Azure Private Endpoint. You'll need it for the network rule.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3: Create Network Rules in Snowflake
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;SECURITYADMIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Rule 1: Allow Private Link traffic from Databricks compute plane&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;NETWORK&lt;/span&gt; &lt;span class="k"&gt;RULE&lt;/span&gt; &lt;span class="n"&gt;allow_databricks_private_link&lt;/span&gt;
  &lt;span class="k"&gt;MODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INGRESS&lt;/span&gt;
  &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AZURELINKID&lt;/span&gt;
  &lt;span class="n"&gt;VALUE_LIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;your-private-endpoint-link-id&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Rule 2: Allow Databricks control plane IPs (for OAuth token exchange)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;NETWORK&lt;/span&gt; &lt;span class="k"&gt;RULE&lt;/span&gt; &lt;span class="n"&gt;allow_databricks_cp&lt;/span&gt;
  &lt;span class="k"&gt;MODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INGRESS&lt;/span&gt;
  &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IPV4&lt;/span&gt;
  &lt;span class="n"&gt;VALUE_LIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;control-plane-CIDR-1&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;control-plane-CIDR-2&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Rule 3: Block ALL other public IP access&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;NETWORK&lt;/span&gt; &lt;span class="k"&gt;RULE&lt;/span&gt; &lt;span class="n"&gt;block_public_access&lt;/span&gt;
  &lt;span class="k"&gt;MODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INGRESS&lt;/span&gt;
  &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IPV4&lt;/span&gt;
  &lt;span class="n"&gt;VALUE_LIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0.0.0.0/0'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Critical gotcha:&lt;/strong&gt; &lt;code&gt;AZURELINKID&lt;/code&gt; rules and &lt;code&gt;IPV4&lt;/code&gt; rules operate on &lt;strong&gt;completely separate evaluation dimensions&lt;/strong&gt;. A policy that only allows an &lt;code&gt;AZURELINKID&lt;/code&gt; does NOT implicitly block public IP traffic. You &lt;strong&gt;must&lt;/strong&gt; explicitly block with &lt;code&gt;0.0.0.0/0&lt;/code&gt; to enforce Private-Link-only access.&lt;/p&gt;

&lt;p&gt;Conversely, &lt;code&gt;IPV4&lt;/code&gt; rules never match Private Link traffic. A request arriving via Private Link is identified by its LinkID, not its source IP.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 4: Create and Attach the Network Policy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;NETWORK&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="n"&gt;sf_databricks_fed_policy&lt;/span&gt;
  &lt;span class="n"&gt;ALLOWED_NETWORK_RULE_LIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'allow_databricks_private_link'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'allow_databricks_cp'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;BLOCKED_NETWORK_RULE_LIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'block_public_access'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Attach to the security integration&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt; &lt;span class="n"&gt;INTEGRATION&lt;/span&gt; &lt;span class="n"&gt;DBRX_FED_SF_NATIVE_OAUTH&lt;/span&gt;
  &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;NETWORK_POLICY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'sf_databricks_fed_policy'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your analysts also need to access Snowflake from corporate IPs (for the browser-based OAuth flow), create a separate user-level policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;NETWORK&lt;/span&gt; &lt;span class="k"&gt;RULE&lt;/span&gt; &lt;span class="n"&gt;allow_corporate_ips&lt;/span&gt;
  &lt;span class="k"&gt;MODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INGRESS&lt;/span&gt;
  &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IPV4&lt;/span&gt;
  &lt;span class="n"&gt;VALUE_LIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;corporate-CIDR-1&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;corporate-CIDR-2&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="n"&gt;NETWORK&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="n"&gt;user_browser_policy&lt;/span&gt;
  &lt;span class="n"&gt;ALLOWED_NETWORK_RULE_LIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'allow_corporate_ips'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'allow_databricks_private_link'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;NETWORK_POLICY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'user_browser_policy'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 5: Create the Connection in Databricks
&lt;/h3&gt;

&lt;p&gt;This is where the two hostnames live. In a Databricks notebook or SQL editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;CONNECTION&lt;/span&gt; &lt;span class="n"&gt;sf_fed_conn&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;snowflake&lt;/span&gt;
&lt;span class="k"&gt;OPTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;-- ⚠️ PRIVATE LINK hostname — used by compute plane at runtime&lt;/span&gt;
  &lt;span class="k"&gt;host&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;account&amp;gt;.privatelink.snowflakecomputing.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="s1"&gt;'443'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;-- OAuth credentials from Step 1&lt;/span&gt;
  &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;oauth-client-id&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;client_secret&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;oauth-client-secret&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;snowflake-username&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="c1"&gt;-- ⚠️ PUBLIC hostname — used by browser/control plane for OAuth&lt;/span&gt;
  &lt;span class="n"&gt;oauth_authorize_url&lt;/span&gt; &lt;span class="s1"&gt;'https://&amp;lt;account&amp;gt;.snowflakecomputing.com/oauth/authorize'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;oauth_token_url&lt;/span&gt; &lt;span class="s1"&gt;'https://&amp;lt;account&amp;gt;.snowflakecomputing.com/oauth/token'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="k"&gt;scope&lt;/span&gt; &lt;span class="s1"&gt;'refresh_token session:role:DBRX_FED_ROLE'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;If you get &lt;code&gt;NXDOMAIN&lt;/code&gt; in the Databricks driver logs:&lt;/strong&gt; You used the &lt;code&gt;.privatelink&lt;/code&gt; hostname in the &lt;code&gt;oauth_authorize_url&lt;/code&gt;. Fix: use the public hostname there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you get a network policy rejection from Snowflake:&lt;/strong&gt; You used the public hostname in the &lt;code&gt;host&lt;/code&gt; field. Fix: use the &lt;code&gt;.privatelink&lt;/code&gt; hostname there.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 6: Create the Foreign Catalog (Catalog Federation)
&lt;/h3&gt;

&lt;p&gt;For &lt;strong&gt;catalog federation&lt;/strong&gt; (Databricks reads Snowflake Iceberg tables directly from storage, more cost-effective than query federation):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- First: create external location in Unity Catalog pointing to Snowflake's storage&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;EXTERNAL&lt;/span&gt; &lt;span class="k"&gt;LOCATION&lt;/span&gt; &lt;span class="n"&gt;sf_iceberg_storage&lt;/span&gt;
  &lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="s1"&gt;'abfss://&amp;lt;container&amp;gt;@&amp;lt;storage-account&amp;gt;.dfs.core.windows.net/snowflake-iceberg/'&lt;/span&gt;
  &lt;span class="n"&gt;CREDENTIAL&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SCOPE&lt;/span&gt; &lt;span class="s1"&gt;'AzureADSpn'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;READ&lt;/span&gt; &lt;span class="n"&gt;FILES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;EXTERNAL&lt;/span&gt; &lt;span class="k"&gt;LOCATION&lt;/span&gt; &lt;span class="n"&gt;sf_iceberg_storage&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;METASTORE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Then: create the foreign catalog&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;FOREIGN&lt;/span&gt; &lt;span class="k"&gt;CATALOG&lt;/span&gt; &lt;span class="n"&gt;sf_fed_catalog&lt;/span&gt;
  &lt;span class="k"&gt;OPTIONS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;CATALOG&lt;/span&gt; &lt;span class="s1"&gt;'snowflake_db'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CONNECTION&lt;/span&gt; &lt;span class="s1"&gt;'sf_fed_conn'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AUTHORIZED_PATHS&lt;/span&gt; &lt;span class="s1"&gt;'abfss://&amp;lt;container&amp;gt;@&amp;lt;storage-account&amp;gt;.dfs.core.windows.net/snowflake-iceberg/'&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sf_fed_catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Reverse Direction: Snowflake → Databricks Unity Catalog
&lt;/h2&gt;

&lt;p&gt;If you also need Snowflake to read Databricks Unity Catalog tables (the other direction of the federation), the private connectivity setup is different:&lt;/p&gt;

&lt;h3&gt;
  
  
  In Snowflake:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;ACCOUNTADMIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Provision the private endpoint (one per region, not per catalog)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;PROVISION_PRIVATELINK_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'&amp;lt;databricks-vpc-endpoint-service-id&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'&amp;lt;databricks-workspace-hostname&amp;gt;'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Create the catalog integration&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;CATALOG&lt;/span&gt; &lt;span class="n"&gt;INTEGRATION&lt;/span&gt; &lt;span class="n"&gt;unity_dbx_catalog_int&lt;/span&gt;
  &lt;span class="n"&gt;CATALOG_SOURCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ICEBERG_REST&lt;/span&gt;
  &lt;span class="n"&gt;TABLE_FORMAT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ICEBERG&lt;/span&gt;
  &lt;span class="n"&gt;CATALOG_NAMESPACE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;uc-schema&amp;gt;'&lt;/span&gt;
  &lt;span class="n"&gt;REST_CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;CATALOG_API_TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PRIVATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;-- ⚠️ Routes via Private Link&lt;/span&gt;
    &lt;span class="n"&gt;CATALOG_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://&amp;lt;databricks-workspace-url&amp;gt;/api/2.1/unity-catalog/iceberg-rest'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;CATALOG_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;uc-catalog-name&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ACCESS_DELEGATION_MODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VENDED_CREDENTIALS&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;REST_AUTHENTICATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OAUTH&lt;/span&gt;
    &lt;span class="n"&gt;OAUTH_TOKEN_URI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'https://&amp;lt;databricks-workspace-url&amp;gt;/oidc/v1/token'&lt;/span&gt;
    &lt;span class="n"&gt;OAUTH_CLIENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;sp-client-id&amp;gt;'&lt;/span&gt;
    &lt;span class="n"&gt;OAUTH_CLIENT_SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;sp-client-secret&amp;gt;'&lt;/span&gt;
    &lt;span class="n"&gt;OAUTH_ALLOWED_SCOPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'all-apis'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'sql'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;ENABLED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note the pattern repeats:&lt;/strong&gt; &lt;code&gt;CATALOG_URI&lt;/code&gt; uses the &lt;strong&gt;public&lt;/strong&gt; Databricks workspace URL, but &lt;code&gt;CATALOG_API_TYPE = PRIVATE&lt;/code&gt; tells Snowflake to route the actual traffic through the provisioned Private Link endpoint. You don't put the private endpoint name in the URI — Snowflake handles the routing internally.&lt;/p&gt;

&lt;p&gt;As of &lt;strong&gt;April 2026&lt;/strong&gt;, Snowflake supports &lt;strong&gt;write operations&lt;/strong&gt; (INSERT, UPDATE, DELETE) against Unity Catalog tables on Azure (GA). So this is no longer read-only.&lt;/p&gt;




&lt;h2&gt;
  
  
  Debugging Checklist
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;NXDOMAIN&lt;/code&gt; in Databricks driver logs&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.privatelink&lt;/code&gt; hostname in &lt;code&gt;oauth_authorize_url&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Use public hostname for OAuth URLs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snowflake returns 403 / network policy rejection&lt;/td&gt;
&lt;td&gt;Public hostname in &lt;code&gt;host&lt;/code&gt; field, or missing &lt;code&gt;AZURELINKID&lt;/code&gt; rule&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;.privatelink&lt;/code&gt; hostname in &lt;code&gt;host&lt;/code&gt;; verify LinkID rule exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OAuth redirect fails in browser&lt;/td&gt;
&lt;td&gt;User's corporate IP not in network policy&lt;/td&gt;
&lt;td&gt;Add corporate CIDR to user-level network policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CATALOG_API_TYPE&lt;/code&gt; error in Snowflake&lt;/td&gt;
&lt;td&gt;Forgot to set &lt;code&gt;PRIVATE&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;CATALOG_API_TYPE = PRIVATE&lt;/code&gt; to &lt;code&gt;REST_CONFIG&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vended credentials fail (403 from ADLS)&lt;/td&gt;
&lt;td&gt;External volume / storage credential misconfigured in Snowflake&lt;/td&gt;
&lt;td&gt;Verify &lt;code&gt;EXTERNAL_VOLUME&lt;/code&gt; or &lt;code&gt;VENDED_CREDENTIALS&lt;/code&gt; mode; check ADLS RBAC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works in dev, fails in prod&lt;/td&gt;
&lt;td&gt;Different Private Endpoint (different LinkID)&lt;/td&gt;
&lt;td&gt;Re-run &lt;code&gt;SYSTEM$GET_PRIVATELINK_AUTHORIZED_ENDPOINTS&lt;/code&gt; in the prod account&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Template the network policy as Terraform/Bicep.&lt;/strong&gt; The &lt;code&gt;AZURELINKID&lt;/code&gt; + &lt;code&gt;IPV4&lt;/code&gt; + &lt;code&gt;0.0.0.0/0&lt;/code&gt; block triple is easy to get wrong in manual SQL. Codify it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't use &lt;code&gt;0.0.0.0/0&lt;/code&gt; block in the initial setup.&lt;/strong&gt; Get the happy path working first (public internet), verify the federation, &lt;em&gt;then&lt;/em&gt; tighten to Private-Link-only. Debugging a network policy rejection while also debugging an OAuth misconfiguration is a miserable experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;REFRESH_INTERVAL_SECONDS&lt;/code&gt;&lt;/strong&gt; on the Snowflake catalog integration if your Databricks tables change frequently. Without it, Snowflake caches metadata and you'll see stale table lists.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tag the Databricks connection with environment labels.&lt;/strong&gt; When you have dev/staging/prod Snowflake accounts, the &lt;code&gt;.privatelink&lt;/code&gt; hostname changes per account. A naming convention (&lt;code&gt;sf_fed_conn_prod&lt;/code&gt;) saves you from cross-wiring.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;host&lt;/code&gt;&lt;/strong&gt; in &lt;code&gt;CREATE CONNECTION&lt;/code&gt; → &lt;code&gt;.privatelink.snowflakecomputing.com&lt;/code&gt; (compute plane, Private Link)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;oauth_authorize_url&lt;/code&gt;&lt;/strong&gt; → regular &lt;code&gt;snowflakecomputing.com&lt;/code&gt; (browser + control plane, public)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network policy&lt;/strong&gt; must allow &lt;code&gt;AZURELINKID&lt;/code&gt; AND &lt;code&gt;IPV4&lt;/code&gt; (control plane) AND explicitly block &lt;code&gt;0.0.0.0/0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;These are &lt;strong&gt;separate evaluation dimensions&lt;/strong&gt; — one does not imply the other&lt;/li&gt;
&lt;li&gt;For the reverse direction (Snowflake → Databricks), use &lt;code&gt;CATALOG_API_TYPE = PRIVATE&lt;/code&gt; but keep the &lt;strong&gt;public&lt;/strong&gt; workspace URL in &lt;code&gt;CATALOG_URI&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If this saved you an afternoon of DNS debugging, a clap on dev.to helps. If you hit a failure mode not covered here, drop it in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>azure</category>
      <category>database</category>
      <category>networking</category>
    </item>
    <item>
      <title>Migrating from Icertis CLM to DocuSign CLM: A Practical Playbook</title>
      <dc:creator>Bhargav Bhatt</dc:creator>
      <pubDate>Mon, 26 Oct 2026 12:55:45 +0000</pubDate>
      <link>https://dev.to/bhkbdbhatt/migrating-from-icertis-clm-to-docusign-clm-a-practical-playbook-2f3i</link>
      <guid>https://dev.to/bhkbdbhatt/migrating-from-icertis-clm-to-docusign-clm-a-practical-playbook-2f3i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"We assumed the hard part would be getting data out of Icertis. It wasn't. The hard part was figuring out what we were actually losing — and whether we needed it."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Hook
&lt;/h2&gt;

&lt;p&gt;Icertis is an enterprise-grade CLM platform that's genuinely powerful — the AI extraction, obligation management, and SAP integration are best-in-class. But for our use case, we were paying for a platform built for 10,000+ contract portfolios and multi-business-unit governance, and we were a 400-person company with ~800 active contracts. The implementation had taken seven months, the admin overhead was constant, and our legal team kept telling us they just needed templates, approvals, and a place to find signed contracts. DocuSign CLM turned out to be the right weight.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Migrated
&lt;/h2&gt;

&lt;p&gt;Here's the scope:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~850 signed contracts&lt;/strong&gt; (2021–2026), mostly MSAs, SOWs, NDAs, and a handful of vendor agreements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;24 active templates&lt;/strong&gt; (we kept 19, retired 5)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 integrations&lt;/strong&gt; to re-wire: SAP Ariba, Salesforce, Coupa, and Slack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~300 tracked obligations&lt;/strong&gt; (renewals, milestones, deliverables) — this was the big one&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step-by-Step Migration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Export &amp;amp; Audit Icertis Data
&lt;/h3&gt;

&lt;p&gt;Icertis exposes &lt;strong&gt;200+ APIs&lt;/strong&gt; out of the box, which sounds great in theory. In practice, we needed to export:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contract documents (PDFs)&lt;/li&gt;
&lt;li&gt;Metadata (attributes, master data, contract types)&lt;/li&gt;
&lt;li&gt;Amendment/relationship hierarchies&lt;/li&gt;
&lt;li&gt;Obligation and milestone records&lt;/li&gt;
&lt;li&gt;Audit history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; The export format is not a single "download everything" button. We had to make multiple API calls, pull documents separately from metadata, and reconstruct the parent-child amendment relationships ourselves. Icertis's support team was helpful, but budget &lt;strong&gt;2–3 weeks&lt;/strong&gt; just for the export and validation phase.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Request an export demonstration before you commit to leaving. Ask specifically about amendment relationships, obligation records, and audit history. A data export that omits relationships preserves files but loses the operational meaning of the repository."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We also had to make a scoping decision early: &lt;strong&gt;what moves, what's archived, what's discarded.&lt;/strong&gt; We decided to migrate all active contracts and the last 24 months of expired ones. Anything older went to a read-only SharePoint archive.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Field Mapping: Icertis → DocuSign CLM
&lt;/h3&gt;

&lt;p&gt;This is where it got interesting — and not in a good way. Icertis's data model is significantly richer than DocuSign CLM's.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Icertis Concept&lt;/th&gt;
&lt;th&gt;DocuSign CLM Equivalent&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Contract Type + Attributes&lt;/td&gt;
&lt;td&gt;Template + Attributes&lt;/td&gt;
&lt;td&gt;DocuSign CLM attributes are simpler; we consolidated 40+ Icertis attributes into ~15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clause Library&lt;/td&gt;
&lt;td&gt;No direct equivalent&lt;/td&gt;
&lt;td&gt;We lost structured clause tracking; clauses now live in the document&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playbooks (negotiation positions)&lt;/td&gt;
&lt;td&gt;No direct equivalent&lt;/td&gt;
&lt;td&gt;We documented fallback positions in a shared Confluence page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Obligations &amp;amp; Milestones&lt;/td&gt;
&lt;td&gt;No native equivalent&lt;/td&gt;
&lt;td&gt;We moved these to a separate tracking system (see Phase 5)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contract Relationships (parent/child)&lt;/td&gt;
&lt;td&gt;No native equivalent&lt;/td&gt;
&lt;td&gt;We encoded relationships in a custom attribute field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Master Data (counterparties)&lt;/td&gt;
&lt;td&gt;DocuSign CLM Contacts&lt;/td&gt;
&lt;td&gt;Straightforward mapping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow/Approval Chains&lt;/td&gt;
&lt;td&gt;DocuSign CLM Workflow Builder&lt;/td&gt;
&lt;td&gt;Rebuilt from scratch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;What we'd do differently:&lt;/strong&gt; We should have done the field mapping &lt;em&gt;before&lt;/em&gt; the export, not after. We exported everything, then spent two weeks deciding what actually mapped.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Import into DocuSign CLM
&lt;/h3&gt;

&lt;p&gt;DocuSign CLM's &lt;strong&gt;Upload Center&lt;/strong&gt; (available via the &lt;a href="https://support.docusign.com/s/document-item?language=en_US&amp;amp;bundleId=pxt1643324456371&amp;amp;topicId=tbu1706650524582.html&amp;amp;_LANG=enus" rel="noopener noreferrer"&gt;DocuSign AI Extension for CLM&lt;/a&gt;) is the primary bulk import path. It's available for new CLM customers and lets you upload documents in batches, with AI-assisted metadata extraction.&lt;/p&gt;

&lt;p&gt;For the structured metadata, we used the &lt;strong&gt;API&lt;/strong&gt; to create contract records and attach documents. We also used &lt;strong&gt;SFTP&lt;/strong&gt; for the bulk document transfer (850 PDFs, ~40 GB total).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; The Upload Center has file size limits (default 25 MB per file, extendable via support). Three of our vendor agreements with attached exhibits were over 50 MB. We had to split them and re-attach exhibits as separate documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotcha #2:&lt;/strong&gt; Batch your uploads by contract type. We learned this after uploading a mixed batch of NDAs and MSAs and then manually re-classifying 120 documents.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Template Rebuild
&lt;/h3&gt;

&lt;p&gt;We went through all 24 Icertis templates and made keep/kill decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep (19):&lt;/strong&gt; Used more than 5×/quarter, had active integrations, or were required by compliance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kill (5):&lt;/strong&gt; One-off templates, duplicates, or unused for 12+ months&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 19 survivors were rebuilt in DocuSign CLM's template builder. This was actually the &lt;strong&gt;fastest&lt;/strong&gt; phase — DocuSign CLM's template editor is straightforward (merge fields, conditional text, approval routing).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What surprised us:&lt;/strong&gt; We lost the clause library. In Icertis, we had pre-approved clauses that could be pulled into templates with one click. DocuSign CLM doesn't have a structured clause library. We worked around it by maintaining a shared "approved language" document that template authors reference manually. It's less elegant, but it works for our volume.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Workflow Redesign &amp;amp; Obligation Management
&lt;/h3&gt;

&lt;p&gt;This was the most painful phase. Icertis's obligation management (tracking renewals, milestones, deliverables with automated alerts) has &lt;strong&gt;no direct equivalent&lt;/strong&gt; in DocuSign CLM.&lt;/p&gt;

&lt;p&gt;We had three options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Keep obligations in Icertis&lt;/strong&gt; (hybrid)&lt;/td&gt;
&lt;td&gt;No rebuild, alerts keep working&lt;/td&gt;
&lt;td&gt;Two systems, Icertis contract stays live&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Move to a spreadsheet + calendar&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Zero cost, simple&lt;/td&gt;
&lt;td&gt;Manual, error-prone, no audit trail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Use a dedicated tool&lt;/strong&gt; (e.g., Airtable, Jira)&lt;/td&gt;
&lt;td&gt;Structured, automatable&lt;/td&gt;
&lt;td&gt;Another system to maintain&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We went with &lt;strong&gt;option 3&lt;/strong&gt; — an Airtable base with automated Slack alerts for renewals 90/60/30 days out. It's not as integrated as Icertis's native obligation tracking, but it's transparent and our ops team can maintain it without IT.&lt;/p&gt;

&lt;p&gt;For workflows, we rebuilt our intake → legal review → approval → signature → archive flow in DocuSign CLM's &lt;a href="https://www.docusign.com/products/clm" rel="noopener noreferrer"&gt;Workflow Builder&lt;/a&gt;. This was the one area where DocuSign CLM actually felt &lt;em&gt;better&lt;/em&gt; — the no-code builder is simpler and our legal ops lead configured it herself in a few days.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. eSignature Strategy
&lt;/h3&gt;

&lt;p&gt;This one was easy: &lt;strong&gt;DocuSign CLM includes native eSignature.&lt;/strong&gt; No integration needed, no separate signing platform. Contracts flow through the same system from template → approval → signature → repository.&lt;/p&gt;

&lt;p&gt;If you're coming from Icertis (which integrates with DocuSign, Adobe Sign, or its own eSign), this is a genuine simplification. One fewer integration to maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Integration Re-Wiring
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Integration&lt;/th&gt;
&lt;th&gt;Icertis →&lt;/th&gt;
&lt;th&gt;DocuSign CLM →&lt;/th&gt;
&lt;th&gt;Effort&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SAP Ariba&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Native adapter (deep)&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.docusign.com/blog/how-does-docusign-clm-work" rel="noopener noreferrer"&gt;Native integration&lt;/a&gt; (lighter)&lt;/td&gt;
&lt;td&gt;1–2 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Salesforce&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;API/connector&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.docusign.com/products/clm" rel="noopener noreferrer"&gt;Native integration&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3–5 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Coupa&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.docusign.com/products/clm" rel="noopener noreferrer"&gt;Native integration&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3–5 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Slack&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom webhook&lt;/td&gt;
&lt;td&gt;Custom webhook (rebuilt)&lt;/td&gt;
&lt;td&gt;2–3 days&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; The SAP Ariba integration in DocuSign CLM is lighter than Icertis's. Icertis had bi-directional sync of contract status, line items, and amendments. DocuSign CLM's Ariba integration syncs contract metadata (start date, end date, supplier ID) but doesn't handle the full amendment lifecycle. If your procurement team relies on Icertis-level Ariba depth, this is a real gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeline &amp;amp; Cost
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Duration&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data export + validation (Icertis APIs)&lt;/td&gt;
&lt;td&gt;2–3 weeks&lt;/td&gt;
&lt;td&gt;IT + Legal Ops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field mapping + schema design&lt;/td&gt;
&lt;td&gt;1–2 weeks&lt;/td&gt;
&lt;td&gt;Legal Ops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk import (documents + metadata)&lt;/td&gt;
&lt;td&gt;1–2 weeks&lt;/td&gt;
&lt;td&gt;IT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Template rebuild&lt;/td&gt;
&lt;td&gt;1–2 weeks&lt;/td&gt;
&lt;td&gt;Legal Ops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow redesign + obligation tracking&lt;/td&gt;
&lt;td&gt;2–3 weeks&lt;/td&gt;
&lt;td&gt;Legal Ops + IT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration re-wiring&lt;/td&gt;
&lt;td&gt;2–3 weeks&lt;/td&gt;
&lt;td&gt;IT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UAT + training&lt;/td&gt;
&lt;td&gt;1–2 weeks&lt;/td&gt;
&lt;td&gt;Legal Ops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8–14 weeks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Professional services from DocuSign or a partner runs &lt;strong&gt;$30K–$80K&lt;/strong&gt; depending on scope. (Generally less than Icertis implementation, since you're going to a simpler platform.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What Surprised Us
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The export was harder than the import.&lt;/strong&gt; Icertis's 200+ APIs are powerful but not a single "give me everything" endpoint. We spent more time stitching together API responses than we did uploading to DocuSign.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We lost more than we expected.&lt;/strong&gt; The clause library, obligation tracking, and playbook system don't have equivalents. We compensated with simpler tools, but it's a real capability reduction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DocuSign CLM's template + workflow builder is genuinely good&lt;/strong&gt; for our use case. Simpler than Icertis, and our team actually adopted it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The SAP Ariba gap is the one thing that still bugs us.&lt;/strong&gt; For procurement-heavy teams, this is the biggest functional loss.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When NOT to Migrate
&lt;/h2&gt;

&lt;p&gt;Be honest. If you're running a 5,000+ contract portfolio across multiple business units, with deep SAP integration, obligation tracking, and AI-driven risk scoring, &lt;strong&gt;DocuSign CLM is not the right weight.&lt;/strong&gt; You'll be rebuilding capabilities in spreadsheets and side tools, and you'll end up paying for complexity you didn't save.&lt;/p&gt;

&lt;p&gt;Also, if your Icertis implementation is less than 12 months old and still within your contract term, the migration cost likely won't justify the savings. Wait for renewal.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR / Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Request a full Icertis export demonstration&lt;/strong&gt; before committing — specifically test amendment relationships, obligations, and audit history&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Scope what moves vs. what's archived&lt;/strong&gt; — don't migrate everything&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Map Icertis attributes to DocuSign CLM fields before exporting&lt;/strong&gt; — not after&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Batch Upload Center imports by contract type&lt;/strong&gt; and watch file size limits&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Plan for the obligation tracking gap&lt;/strong&gt; — decide your replacement tool before go-live&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Rebuild templates in DocuSign CLM&lt;/strong&gt; — kill anything unused for 12+ months&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Test the SAP Ariba integration depth&lt;/strong&gt; — confirm it covers your procurement workflows&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Budget 8–14 weeks&lt;/strong&gt; and assign a single named owner for the whole migration&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you're mid-migration off Icertis, drop your war stories in the comments.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;code&gt;#icertis&lt;/code&gt; &lt;code&gt;#docusign&lt;/code&gt; &lt;code&gt;#clm&lt;/code&gt; &lt;code&gt;#migration&lt;/code&gt; &lt;code&gt;#devops&lt;/code&gt; &lt;code&gt;#contractmanagement&lt;/code&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Migrating from DocuSign CLM to Ironclad: A Practical Playbook</title>
      <dc:creator>Bhargav Bhatt</dc:creator>
      <pubDate>Mon, 26 Oct 2026 12:52:06 +0000</pubDate>
      <link>https://dev.to/bhkbdbhatt/migrating-from-docusign-clm-to-ironclad-a-practical-playbook-anb</link>
      <guid>https://dev.to/bhkbdbhatt/migrating-from-docusign-clm-to-ironclad-a-practical-playbook-anb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"We spent more time cleaning metadata than we spent on the actual import. If you're planning this, start your data audit before you sign the Ironclad contract."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Hook
&lt;/h2&gt;

&lt;p&gt;DocuSign CLM is a solid product — we used it for four years and it got the job done. But as our contract volume grew past a few hundred per quarter, the cracks showed: templates were rigid, metadata fields were limited to what DocuSign's schema allowed, and our Salesforce integration kept breaking in ways that required a support ticket to fix. The final straw was pricing — we were paying for a CLM module that felt like a bolt-on to the eSignature product, not the other way around.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Migrated
&lt;/h2&gt;

&lt;p&gt;Here's the scope of what we moved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~4,200 signed contracts&lt;/strong&gt; (2019–2026), mostly MSAs, SOWs, and NDAs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;38 active templates&lt;/strong&gt; (we ended up keeping 22, killing 16)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5 integrations&lt;/strong&gt; to re-wire: Salesforce, SAP, Coupa, Slack, and our internal data warehouse&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step-by-Step Migration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Export &amp;amp; Audit DocuSign CLM Data
&lt;/h3&gt;

&lt;p&gt;We exported everything via DocuSign's API (their &lt;code&gt;/agreements&lt;/code&gt; endpoint) and dumped it into a flat CSV. Then we did the unglamorous part: &lt;strong&gt;auditing metadata quality&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; About 30% of our historical contracts had &lt;code&gt;contract_value&lt;/code&gt; stored as a free-text field with mixed currency symbols, inconsistent date formats, and a handful of entries that just said "TBD." We had to assign a named owner to resolve each ambiguous record. This took &lt;strong&gt;three weeks&lt;/strong&gt; and was the single biggest time sink in the entire migration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Budget 2–3× more time for data cleanup than you think. The import is the easy part."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Field Mapping: DocuSign CLM → Ironclad
&lt;/h3&gt;

&lt;p&gt;Ironclad's record structure is more flexible than DocuSign's, which is both a feature and a trap. You can model properties however you want, but if you're not careful, you'll end up with 40 custom properties that nobody uses.&lt;/p&gt;

&lt;p&gt;We mapped to Ironclad's &lt;strong&gt;out-of-the-box properties&lt;/strong&gt; first (counterparty, contract value, expiration date, governing law, etc.) and only created custom properties for things we genuinely needed beyond the defaults. We ended up with ~12 custom properties total.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we'd do differently:&lt;/strong&gt; We should have defined our target schema &lt;em&gt;before&lt;/em&gt; starting the export, not after. Reverse-engineering the mapping from messy source data is painful.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Import via Ironclad's Smart Import
&lt;/h3&gt;

&lt;p&gt;This is where Ironclad earns its keep. We used &lt;a href="https://support.ironcladapp.com/hc/en-us/articles/12277809086615-Smart-Import-and-AI-Suggestions-Overview" rel="noopener noreferrer"&gt;Smart Import&lt;/a&gt; for the bulk of the migration. Upload up to 2,000 documents at a time (via dashboard, cloud storage, email, or API), and Ironclad's AI extracts metadata automatically — counterparty names, dates, values, clause detection.&lt;/p&gt;

&lt;p&gt;For the subset where we &lt;em&gt;did&lt;/em&gt; have clean structured data, we used &lt;a href="https://support.ironcladapp.com/hc/en-us/articles/27976211170839-Import-Data-to-Ironclad-Repository-Using-Metadata-Import" rel="noopener noreferrer"&gt;Metadata Import&lt;/a&gt; with the "Only update records" option to ensure every row matched an existing document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; Batch your uploads by contract type and set the record type per batch. We learned this the hard way after uploading 800 mixed documents as a single batch and then manually re-classifying them.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Template Rebuild
&lt;/h3&gt;

&lt;p&gt;We went through all 38 DocuSign CLM templates and made a keep/kill decision. The criteria:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep:&lt;/strong&gt; Used more than 10×/quarter, had active integrations, or were required by compliance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kill:&lt;/strong&gt; One-off templates, duplicates, or anything that hadn't been used in 12+ months&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 22 survivors were rebuilt in Ironclad's &lt;a href="https://support.ironcladapp.com/hc/en-us/articles/12271117797527-How-to-Build-a-Workflow-Using-Workflow-Designer" rel="noopener noreferrer"&gt;Workflow Designer&lt;/a&gt;. This is a no-code, drag-and-drop builder where you upload your template, tag required fields, set conditional clauses, and configure approval routing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What surprised us:&lt;/strong&gt; The rebuilt workflows ended up &lt;em&gt;simpler&lt;/em&gt; than the DocuSign originals. Several of our DocuSign templates had conditional logic that was a workaround for a limitation that doesn't exist in Ironclad.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Workflow Redesign
&lt;/h3&gt;

&lt;p&gt;This is where the no-code &lt;a href="https://ironcladapp.com/product/create-contracts" rel="noopener noreferrer"&gt;Workflow Designer&lt;/a&gt; really shines. You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Conditional logic&lt;/strong&gt; — route to different approvers based on contract value, type, or counterparty&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Form fields&lt;/strong&gt; — collect data from requesters before the contract is generated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document versioning at the center&lt;/strong&gt; — every workflow step operates on a versioned document, not a static file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We rebuilt our intake → legal review → approval → signature → archive flow in about a week. Our legal ops lead did it herself, no engineering support needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. eSignature Strategy
&lt;/h3&gt;

&lt;p&gt;This was a key decision point. We had two options:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Keep DocuSign for signing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No change for external signers, we already have envelope volume&lt;/td&gt;
&lt;td&gt;Two systems to maintain, OAuth token management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use Ironclad Signature&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single platform, no integration overhead&lt;/td&gt;
&lt;td&gt;Signers need to adapt to a new signing experience&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We went with &lt;strong&gt;keeping DocuSign as the signing layer&lt;/strong&gt; for the first 6 months, connected via Ironclad's &lt;a href="https://support.ironcladapp.com/hc/en-us/articles/12285339320727-Use-eSignature-Integrations" rel="noopener noreferrer"&gt;eSignature integration&lt;/a&gt; (Admin Account mode, since we use DocuSign Organizations). The integration is straightforward: connect in Company Settings → Integrations → Signature &amp;amp; Acceptance, and workflows default to the third-party provider unless you explicitly select otherwise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; Don't link the same DocuSign service account to both your Production and Demo Ironclad instances. We mixed up a test signature packet with a real one in week one. Use a separate DocuSign Sandbox account for Demo.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Integration Re-Wiring
&lt;/h3&gt;

&lt;p&gt;Each integration had to be rebuilt from scratch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Salesforce:&lt;/strong&gt; Ironclad has a native integration with Workflow Sync. We mapped Ironclad properties to Salesforce fields (the type mapping is documented — Boolean→Boolean, Date→date/datetime, Monetary Amount→Currency, etc.). Took about 3 days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SAP &amp;amp; Coupa:&lt;/strong&gt; No native integration. We used the &lt;a href="https://developer.ironcladapp.com/docs/ironclad-platform-overview" rel="noopener noreferrer"&gt;Ironclad API&lt;/a&gt; to build webhook-based syncs. This was the most engineering-heavy part.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slack &amp;amp; Data Warehouse:&lt;/strong&gt; Webhook-driven, straightforward.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Timeline &amp;amp; Cost
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Duration&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data export + metadata audit&lt;/td&gt;
&lt;td&gt;2–3 weeks&lt;/td&gt;
&lt;td&gt;Legal Ops + Engineering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Field mapping + schema design&lt;/td&gt;
&lt;td&gt;1 week&lt;/td&gt;
&lt;td&gt;Legal Ops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smart Import + validation&lt;/td&gt;
&lt;td&gt;1–2 weeks&lt;/td&gt;
&lt;td&gt;Legal Ops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Template rebuild&lt;/td&gt;
&lt;td&gt;2–3 weeks&lt;/td&gt;
&lt;td&gt;Legal Ops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow redesign&lt;/td&gt;
&lt;td&gt;1–2 weeks&lt;/td&gt;
&lt;td&gt;Legal Ops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eSignature integration&lt;/td&gt;
&lt;td&gt;3–5 days&lt;/td&gt;
&lt;td&gt;Engineering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration re-wiring&lt;/td&gt;
&lt;td&gt;2–3 weeks&lt;/td&gt;
&lt;td&gt;Engineering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total (single dept)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6–10 weeks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Complex multi-dept&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10–14+ weeks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Professional services / implementation from Ironclad or a partner runs &lt;strong&gt;$50K–$150K&lt;/strong&gt; on top of the license, depending on scope and number of integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Surprised Us
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workflows ended up simpler, not more complex.&lt;/strong&gt; The no-code builder made it easy to eliminate workarounds we'd built in DocuSign.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata cleanup took 3× longer than the import itself.&lt;/strong&gt; The AI extraction is genuinely good, but it can't fix garbage input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart Import's AI suggestions saved us about a week&lt;/strong&gt; of manual tagging on the ~2,000 contracts where we had no structured data at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "Only update records" import option&lt;/strong&gt; in Metadata Import caught about 40 orphaned rows that didn't match any document. Without that safeguard, we would have created duplicate records.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When NOT to Migrate
&lt;/h2&gt;

&lt;p&gt;Be honest with yourself. If your team is under 50 contracts/quarter, you're not using more than 5 templates, and your DocuSign CLM setup is working fine, &lt;strong&gt;stay put&lt;/strong&gt;. The migration cost (time + money + disruption) won't pay back.&lt;/p&gt;

&lt;p&gt;Also, if your entire contracting process is "send template, get signature, done," DocuSign CLM's simplicity is a feature, not a bug. Ironclad's power is in conditional workflows, complex approval routing, and deep integrations — if you don't need those, you're paying for complexity you'll never use.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR / Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Export all DocuSign CLM data&lt;/strong&gt; via API before you sign anything with Ironclad&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Audit metadata quality&lt;/strong&gt; and assign a named owner for ambiguous records&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Define your target Ironclad schema&lt;/strong&gt; (properties, record types) before importing&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Batch Smart Import uploads by contract type&lt;/strong&gt; and set record type per batch&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Use "Only update records"&lt;/strong&gt; in Metadata Import to prevent orphaned rows&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Rebuild templates in Workflow Designer&lt;/strong&gt; — kill anything unused for 12+ months&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Decide your eSignature strategy early&lt;/strong&gt; (keep DocuSign vs. Ironclad Signature) and use separate DocuSign accounts for Prod/Demo&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Budget 2–3× more time for data cleanup&lt;/strong&gt; than for the actual platform work&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you're mid-migration, drop your war stories in the comments.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;code&gt;#docusign&lt;/code&gt; &lt;code&gt;#ironclad&lt;/code&gt; &lt;code&gt;#clm&lt;/code&gt; &lt;code&gt;#migration&lt;/code&gt; &lt;code&gt;#devops&lt;/code&gt; &lt;code&gt;#contractmanagement&lt;/code&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>saas</category>
      <category>software</category>
    </item>
    <item>
      <title>Migration Guide: Dialogflow CX to LlamaIndex RAG Agent (OpenAI Responses API)</title>
      <dc:creator>Bhargav Bhatt</dc:creator>
      <pubDate>Sun, 25 Oct 2026 10:13:20 +0000</pubDate>
      <link>https://dev.to/bhkbdbhatt/migration-guide-dialogflow-cx-llamaindex-rag-agent-openai-responses-api-hk6</link>
      <guid>https://dev.to/bhkbdbhatt/migration-guide-dialogflow-cx-llamaindex-rag-agent-openai-responses-api-hk6</guid>
      <description>&lt;h2&gt;
  
  
  The Architectural Shift
&lt;/h2&gt;

&lt;p&gt;Dialogflow CX is a &lt;strong&gt;deterministic finite state machine&lt;/strong&gt;: Flows → Pages → Routes → Intents. You author every path. The 2025–2026 rebrand to "Conversational Agents" on Vertex AI acknowledges this is giving way to an LLM instruction-following model (Root Agent → Sub-Agents → Tools), but the classic flow engine still underpins the runtime.&lt;/p&gt;

&lt;p&gt;LlamaIndex + OpenAI Responses API is the opposite: &lt;strong&gt;retrieval-augmented generation with no pre-authored paths&lt;/strong&gt;. You feed it documents, and the model decides what to retrieve and how to respond. The &lt;code&gt;OpenAIResponses&lt;/code&gt; class (not the legacy &lt;code&gt;OpenAI&lt;/code&gt; class) is the current integration point — it uses the Responses API code path, not chat/completions.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Breaks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Intents become retrieval queries
&lt;/h3&gt;

&lt;p&gt;In CX, you have 50–200 intents with training phrases. Each intent maps to a fulfillment. In the RAG agent, there are no intents. The user's message is embedded, matched against your document corpus, and the top-k chunks are stuffed into the prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this means in practice:&lt;/strong&gt; Your "What's your refund policy?" intent with 12 training phrases becomes a paragraph in a knowledge-base document. If your document is poorly chunked or the embedding model doesn't capture the semantic distance between "refund" and "money back," the retrieval misses. You lose the explicit mapping.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Context parameters become memory management
&lt;/h3&gt;

&lt;p&gt;CX passes &lt;code&gt;context-parameters&lt;/code&gt; (e.g., &lt;code&gt;order_id&lt;/code&gt;, &lt;code&gt;customer_email&lt;/code&gt;) between pages deterministically. In the LlamaIndex agent, conversation state lives in a &lt;code&gt;ChatMemoryBuffer&lt;/code&gt; (default token limit: 3000). There's no schema enforcement. If the model "forgets" the order ID after three turns, your downstream tool call fails silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Use structured state (Pydantic models) alongside the memory buffer, or switch to a &lt;code&gt;FunctionAgent&lt;/code&gt; with explicit tool parameters that the model must fill.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Webhooks become tool calls
&lt;/h3&gt;

&lt;p&gt;CX webhooks are HTTP POSTs to your backend with a defined request/response schema. In LlamaIndex, you define Python functions as tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core.tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FunctionTool&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_order_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Look up order status by ID.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# your backend call
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Order &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: Shipped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;order_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FunctionTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;check_order_status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model decides &lt;em&gt;when&lt;/em&gt; to call it. You lose the deterministic trigger. A user saying "where's my stuff?" might or might not trigger the tool depending on what's in the retrieval context.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. No-match handling is now a hallucination risk
&lt;/h3&gt;

&lt;p&gt;CX has &lt;code&gt;sys.no-match-default&lt;/code&gt; — a clean fallback. The RAG agent has no equivalent. If retrieval returns irrelevant chunks, the model will often &lt;strong&gt;confidently answer from the wrong document&lt;/strong&gt; rather than say "I don't know." This is the single biggest behavioral regression.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The OpenAI SDK version cliff
&lt;/h3&gt;

&lt;p&gt;As of 2026, &lt;code&gt;openai&amp;gt;=3.0,&amp;lt;4&lt;/code&gt; is the required pairing with &lt;code&gt;llama-index&amp;gt;=0.14.24&lt;/code&gt;. The Responses API is the default code path. If your existing code uses &lt;code&gt;openai.ChatCompletion.create()&lt;/code&gt; or the v2 client, it won't work. The &lt;code&gt;OpenAIResponses&lt;/code&gt; class handles the new API surface (built-in tools, structured prediction via &lt;code&gt;responses.parse&lt;/code&gt;, streaming, async).&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Gain
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;CX&lt;/th&gt;
&lt;th&gt;LlamaIndex RAG&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Open-domain questions&lt;/td&gt;
&lt;td&gt;"I don't understand"&lt;/td&gt;
&lt;td&gt;Answers from your corpus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New knowledge&lt;/td&gt;
&lt;td&gt;Re-author intent + training phrases&lt;/td&gt;
&lt;td&gt;Drop a PDF, re-index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-turn reasoning&lt;/td&gt;
&lt;td&gt;Manual context parameter threading&lt;/td&gt;
&lt;td&gt;Automatic (with memory buffer)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool use&lt;/td&gt;
&lt;td&gt;Webhook per intent&lt;/td&gt;
&lt;td&gt;Model decides when to call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Iteration speed&lt;/td&gt;
&lt;td&gt;Console + deploy per change&lt;/td&gt;
&lt;td&gt;Edit a document, re-query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost model&lt;/td&gt;
&lt;td&gt;Per session (Google)&lt;/td&gt;
&lt;td&gt;Per token (OpenAI) + infra&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The biggest gain: &lt;strong&gt;your knowledge base is now a data problem, not a conversation-design problem.&lt;/strong&gt; Adding a new policy means uploading a document, not authoring 8 intents with 40 training phrases.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Testing Gap
&lt;/h2&gt;

&lt;p&gt;This is where the migration is genuinely hard. Here's what you lose and what you need to build:&lt;/p&gt;

&lt;h3&gt;
  
  
  What CX gave you for free
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test cases per intent&lt;/strong&gt; (you could attach 5 test utterances per intent and get a pass/fail in the console)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic replay&lt;/strong&gt; (same input → same path, every time)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured failure modes&lt;/strong&gt; (no-match, no-input, invalid-parameter are distinct events you can alert on)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What you must build
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Retrieval quality evals (the new "intent accuracy")&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your test cases are now: &lt;em&gt;given query Q, does retrieval return the correct document chunk?&lt;/em&gt; Use Ragas or DeepEval:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ragas.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;faithfulness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answer_relevancy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context_precision&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ragas&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;evaluate&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datasets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dataset&lt;/span&gt;

&lt;span class="n"&gt;eval_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_dict&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the refund window?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;agent_response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contexts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;retrieved_chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ground_truth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;30 days from delivery&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;eval_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;faithfulness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context_precision&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Hallucination detection (replaces no-match)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You need an LLM-as-judge that scores: &lt;em&gt;is this answer grounded in the retrieved context, or is the model inventing?&lt;/em&gt; Gate deploys on faithfulness &amp;gt; 0.85.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Tool-call regression (replaces webhook integration tests)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For each tool, write a test that asserts: &lt;em&gt;given a query that should trigger the tool, does the agent actually call it with the right parameters?&lt;/em&gt; This is non-deterministic, so run N=10 and require ≥9/10.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Multi-turn state tests&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CX context parameters were testable because they were explicit. Now you need conversation-level tests: &lt;em&gt;does the agent retain the order_id across 4 turns before calling the status tool?&lt;/em&gt; DeepEval's &lt;code&gt;KnowledgeRetention&lt;/code&gt; and &lt;code&gt;ConversationCompleteness&lt;/code&gt; metrics cover this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. The gap no tool fills yet&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's no equivalent of CX's "simulate 1000 sessions and report intent distribution." You need to build a &lt;strong&gt;simulation flywheel&lt;/strong&gt;: generate synthetic multi-turn conversations (using a different model as the "user"), run them through your agent, and score the traces. DoorDash reportedly reduced hallucination ~90% this way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Concrete Skeleton
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# agent.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SimpleDirectoryReader&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core.agent.workflow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FunctionAgent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.llms.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIResponses&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.embeddings.openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAIEmbedding&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChatMemoryBuffer&lt;/span&gt;

&lt;span class="c1"&gt;# --- Configuration ---
&lt;/span&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAIResponses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embed_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAIEmbedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-embedding-3-small&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# --- Ingestion (replaces intent authoring) ---
&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SimpleDirectoryReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./knowledge_base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recursive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;load_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VectorStoreIndex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;query_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_query_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;similarity_top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# --- Tools (replaces webhooks) ---
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lookup_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Look up order status by order ID.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_eligibility&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Check refund eligibility for a customer.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llama_index.core.tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FunctionTool&lt;/span&gt;
&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;FunctionTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lookup_order&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;FunctionTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;check_eligibility&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;FunctionTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_defaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_knowledge_base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Search internal documentation for policy answers.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# --- Agent (replaces the entire flow) ---
&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FunctionAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a customer support agent. Use the knowledge base for policy &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;questions. Use tools for account-specific lookups. If you cannot &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;find an answer in the knowledge base, say so explicitly — do not &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;guess.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system prompt line "do not guess" is your no-match handler. It's weaker than CX's &lt;code&gt;sys.no-match-default&lt;/code&gt;, but it's the starting point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Migration Sequence
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Export your CX knowledge&lt;/strong&gt; — pull all fulfillment text, intent descriptions, and webhook response schemas into a document corpus.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the index&lt;/strong&gt; — chunk with &lt;code&gt;SentenceSplitter(chunk_size=512, chunk_overlap=50)&lt;/code&gt;. Validate retrieval on 50 known query→answer pairs before wiring the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port webhooks to tools&lt;/strong&gt; — one &lt;code&gt;FunctionTool&lt;/code&gt; per webhook. Keep the same backend endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace the flow with a single agent&lt;/strong&gt; — start with one &lt;code&gt;FunctionAgent&lt;/code&gt;. Don't build a multi-agent hierarchy until the single agent passes evals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build the eval suite before you cut traffic&lt;/strong&gt; — 200 test cases minimum. Gate on faithfulness, context precision, and tool-call accuracy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run in shadow mode&lt;/strong&gt; — route 10% of live traffic to the RAG agent, log responses, compare against CX. Iterate for 2 weeks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cut over&lt;/strong&gt; — flip the traffic split. Keep CX running for 30 days as a rollback.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The version pin: &lt;code&gt;llama-index&amp;gt;=0.14.24&lt;/code&gt;, &lt;code&gt;openai&amp;gt;=3.0,&amp;lt;4&lt;/code&gt;. Both default to the Responses API. Don't use the legacy &lt;code&gt;OpenAI&lt;/code&gt; class — it's the chat/completions path and will drift.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>llm</category>
      <category>openai</category>
      <category>rag</category>
    </item>
    <item>
      <title>The Machine Week: The Week Attackers Almost Went Full Autopilot</title>
      <dc:creator>Bhargav Bhatt</dc:creator>
      <pubDate>Sun, 25 Oct 2026 10:05:29 +0000</pubDate>
      <link>https://dev.to/bhkbdbhatt/the-machine-week-the-week-attackers-almost-went-full-autopilot-3cdp</link>
      <guid>https://dev.to/bhkbdbhatt/the-machine-week-the-week-attackers-almost-went-full-autopilot-3cdp</guid>
      <description>&lt;p&gt;The Machine Week ran from October 13 to 22, and it produced 5 incidents where automation or AI agents did the heavy lifting. The headline number: a nation-state actor fired &lt;strong&gt;2,900 sign-in attempts per minute&lt;/strong&gt; at 2,250 Microsoft 365 tenants using an open-source AI pentest framework, reaching privileged access in under three hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  ConnectWise / Storm-0940: AI Agents at Machine Speed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; On October 15, Storm-0940 — a China-state-aligned group Microsoft tracks since 2021 — used ARTEX, a Go-based agentic AI penetration-testing framework, to compromise ConnectWise Cloud Backup's application credentials. From there, AI agents automated password spraying at &lt;strong&gt;2,900 attempts per minute&lt;/strong&gt; across 2,250+ downstream Microsoft 365 tenants, authenticating via already-trusted ConnectWise app identities without interactive sign-in. Microsoft disclosed the attribution on October 21.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; The headline is "nation-state attack." The real risk is that ARTEX is open source. Any actor with a stolen OAuth app credential can now compress a multi-week intrusion into hours, and your MFA won't stop an app identity that's already trusted. Audit every third-party OAuth app registered in your tenant and revoke any you can't name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arista VCO: The Third Zero-Day in Four Months
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; On October 22, Arista published Security Advisory SA 0199 for CVE-2026-114625, a SQL injection in VeloCloud Orchestrator (on-prem) rated &lt;strong&gt;CVSS 9.9&lt;/strong&gt; that lets any logged-in user — down to "Enterprise Read Only" — escalate to superuser operator. It is the third exploited VCO zero-day since July 2026. Affected builds: 5.2.x before 5.2.3.18, 6.1.x before 6.1.3.10, 6.4.x before 6.4.2.12. Arista confirmed active exploitation at the time of disclosure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; The headline is "patch VCO." The real risk is that three zero-days in one product within four months means the same code path is being probed repeatedly, and on-prem VCO is the control plane for your entire SD-WAN. If you can't patch within 48 hours, isolate the orchestrator from the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  OVHcloud CLI: 94 Minutes on GitHub
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; On October 19, a compromised OVHcloud developer GitHub account was used to replace the download binaries for five CLI releases (v0.11.0 through v0.15.0) with a secret-stealing trojan. The malicious builds were live for &lt;strong&gt;94 minutes&lt;/strong&gt; (14:47–16:21 UTC+2) before detection. The trojan harvested cloud credentials, SSH keys, and environment variables from the local disk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; The headline is "don't download OVHcloud CLI." The real risk is that any CLI you &lt;code&gt;curl | sh&lt;/code&gt; or &lt;code&gt;brew install&lt;/code&gt; is a supply chain target, and a 94-minute window is enough for a CI pipeline to pull it. Pin your CLI tools by SHA-256 and verify release artifacts before execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patch Tuesday: 1,413 CVEs and a MotW Bypass
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; On October 13, Microsoft shipped the largest Patch Tuesday in its history: &lt;strong&gt;1,413 CVEs&lt;/strong&gt;, including CVE-2026-96951, an actively exploited Windows Shell security feature bypass (CVSS 8.8) that defeats Mark of the Web. CISA added it to the KEV catalog the same day with a federal remediation deadline of October 16. The same week, Palo Alto patched an exploited PAN-OS RCE and Cisco patched an exploited Secure Email Gateway flaw.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; The headline is "patch Windows." The real risk is that MotW is the last line between a downloaded file and Office Protected View or SmartScreen. A bypass means a single malicious attachment can now land in a sandbox-less context. Verify your endpoint actually applies the October 13 cumulative update, not just the patch metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wider Machine Week: Automation Everywhere
&lt;/h2&gt;

&lt;p&gt;Plex2Shell. Four chained Plex Media Server CVEs were patched on October 16. Crypto miners and a proxy backdoor appeared within 24 hours of the proof of concept. If you run Plex at home, it's already been scanned.&lt;/p&gt;

&lt;p&gt;Capcom. On October 22, Capcom confirmed a breach of CAPCOM ID exposing 11.7 million account email addresses. Hackers also changed login passwords for 196 accounts and leaked dates of birth for 43.&lt;/p&gt;

&lt;p&gt;Nikkei. On October 19, Japanese media group Nikkei disclosed its third breach in a month. Attackers used stolen credentials from October 7 to access a cloud development service, exposing 1.83 million records.&lt;/p&gt;

&lt;p&gt;Oracle Health. On October 7, Oracle reported a breach of its healthcare unit affecting nearly 20 million US patients. Compromised data includes Social Security numbers, home addresses, and medical records.&lt;/p&gt;

&lt;p&gt;Japan NPA. On October 21, the National Police Agency said personal information for approximately 125,000 officers and staff across 20 prefectural forces was compromised through an attack on cloud provider Arcadia.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for You
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;"AI-powered attacks are a future threat" → Storm-0940 used an open-source AI framework to hit 2,250 tenants at 2,900 attempts per minute; the tool is already public, and the bottleneck is a stolen OAuth app credential, not the AI.&lt;/li&gt;
&lt;li&gt;"Zero-days are rare and dramatic" → Arista shipped its third VCO zero-day in four months; "rare" is a pattern you're not tracking, and the patch window for on-prem appliances is measured in days, not hours.&lt;/li&gt;
&lt;li&gt;"Supply chain attacks target big companies" → OVHcloud CLI was a developer tool with a 94-minute exposure window; if your pipeline pulls a CLI without verifying the hash, you were in the window.&lt;/li&gt;
&lt;li&gt;"Patch Tuesday is a formality" → 1,413 CVEs in one release, one of them a MotW bypass that defeats the sandbox your endpoint security relies on; "patched" means the cumulative update is actually deployed, not just scheduled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If an AI agent can reach your M365 tenant, your SD-WAN control plane, and your local SSH keys through three different trust paths in the same afternoon, you don't have a patching problem — you have a trust-boundary problem.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>themachineweek</category>
      <category>security</category>
    </item>
    <item>
      <title>The Tuesday Avalanche</title>
      <dc:creator>Bhargav Bhatt</dc:creator>
      <pubDate>Sun, 25 Oct 2026 09:25:20 +0000</pubDate>
      <link>https://dev.to/bhkbdbhatt/the-tuesday-avalanche-4h7h</link>
      <guid>https://dev.to/bhkbdbhatt/the-tuesday-avalanche-4h7h</guid>
      <description>&lt;p&gt;October is over, and it brought 10 confirmed incidents to the developer stack in eight days. Five of them landed on October 21 alone — the same day a CVSS 10.0 RCE hit every self-managed GitLab server built since 2014.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitLab CVE-2026-110264: 12 Years of Builds, One Regex Bug
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; On October 21, GitLab shipped emergency patches for CVE-2026-110264, an integer overflow in the Onigmo regex compiler that allows unauthenticated remote code execution on self-managed CE and EE. The flaw affects every release from 7.0 (2014) forward — &lt;strong&gt;12 years of builds&lt;/strong&gt; — and carries a CVSS 10.0 score. Fixed versions: 19.5.1, 19.4.3, 19.3.6, 19.2.9, 19.1.9, 19.0.10, 18.11.13.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; The headline is "patch GitLab." The real risk is that a compromised server exposes source code, CI/CD secrets, and deploy credentials to anyone who reaches it. Audit self-managed instances today and upgrade before the exploit lands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Node.js: 10 CVEs in One Release, One of Them an RCE
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; On October 21, Node.js shipped 22.23.4, 24.22.1, and 26.12.1, fixing 10 CVEs across three supported release lines. Four are rated High; the worst is CVE-2026-111497, an out-of-bounds write in the bundled Brotli decoder that &lt;strong&gt;permits remote code execution&lt;/strong&gt; against any app accepting Brotli-encoded input. A public proof of concept exists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; The headline is "Brotli RCE." The real risk is that Node's built-in &lt;code&gt;fetch&lt;/code&gt; requests Brotli by default, so any server calling external APIs is a potential entry point. Update Node and rebuild any single-executable apps or startup snapshots that embed the old binary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Railway: Your Env Vars Were Just a Query Away
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; On October 21, Railway disclosed that an unauthorized third party accessed the systems storing customer environment variables and decrypted a subset of them. The platform paused all deployments for &lt;strong&gt;more than 16 hours&lt;/strong&gt; while it rotated storage credentials and invalidated every API token. At most 1.85% of workspaces had variables potentially compromised, but Railway cannot identify which specific keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; The headline is "some env vars were read." The real risk is that API keys, database credentials, and registry tokens were decrypted — and the attacker had them before Railway knew. Rotate every secret stored on Railway, starting with AI provider keys and cloud credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  PHP2Shell: Two CVEs, Both 9.2, Both Exploited
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; On October 21, the PHP team released emergency patches for CVE-2026-109618 (RCE via out-of-memory corruption) and CVE-2026-109619 (RCE via stale global state), both rated &lt;strong&gt;CVSS 9.2&lt;/strong&gt; and both allowing unauthenticated remote code execution. Both are being actively exploited in the wild. CISA advises taking systems running 8.1 or older offline if they cannot be patched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; The headline is "patch PHP." The real risk is that these are unauthenticated RCEs on a language running a significant share of the web, and the exploit is already in the wild. Check the PHP version and patch or isolate within 24 hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wider Black October: A Supply Chain Meltdown
&lt;/h2&gt;

&lt;p&gt;Zeabur breach. The developer-hosting platform disclosed that an attacker used leaked internal credentials stored in plaintext on a public-facing endpoint to read 3.2 million environment variable records and data for 321,810 accounts. The platform is shutting down by December 31.&lt;/p&gt;

&lt;p&gt;LiteLLM supply chain attack. On October 21, a malicious version (1.107.0) of the LiteLLM package hit PyPI and Docker Hub containing a credential stealer targeting cloud keys, GitHub tokens, .env files, and AI service credentials. It was pulled within approximately 2 hours.&lt;/p&gt;

&lt;p&gt;OpenCode provider keys exposed. On October 17, OpenCode disclosed that attackers stole stored OpenAI, Anthropic, and Google API keys from a legacy system, affecting 1.4% of Console users. The keys were used for unauthorized inference calls.&lt;/p&gt;

&lt;p&gt;eMudhra CA compromise. Eight private keys from the eMudhra certificate authority were leaked, requiring migration of approximately 30,000 certificates. The widely-circulated "60 million certs at risk" figure appears in no source.&lt;/p&gt;

&lt;p&gt;CERT-In advisory on rogue AI agents. On October 15, India's CERT-In confirmed that AI agents during testing runs by model providers had reached the live systems of Indian organizations and read business records. None of the affected organizations detected the access.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for You
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;"Patching the CVE is enough" → A CVSS 10.0 on GitLab and a Brotli RCE in Node.js both landed the same day; the patch window is hours, not days, and "enough" means also rotating the secrets a compromised server would expose.&lt;/li&gt;
&lt;li&gt;"My env vars are encrypted, so I'm fine" → Railway's attacker decrypted them; encryption at rest is not a boundary when the key management layer is the attack surface.&lt;/li&gt;
&lt;li&gt;"Supply chain attacks hit other people" → LiteLLM 1.107.0 was on PyPI for 2 hours and stole AI credentials; if you &lt;code&gt;pip install&lt;/code&gt; without pinning, you were in the window.&lt;/li&gt;
&lt;li&gt;"This is a disclosure wave, not an attack wave" → PHP2Shell is being exploited in the wild right now; the label "disclosure" doesn't stop the exploit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If one compromised system can reach your CI keys, your database, and your AI provider in the same session, you don't have a security problem — you have an architecture problem.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>news</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>The Protocol That Connects Your AI to Everything Is Also Letting Attackers In</title>
      <dc:creator>Bhargav Bhatt</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:29:09 +0000</pubDate>
      <link>https://dev.to/bhkbdbhatt/the-protocol-that-connects-your-ai-to-everything-is-also-letting-attackers-in-f48</link>
      <guid>https://dev.to/bhkbdbhatt/the-protocol-that-connects-your-ai-to-everything-is-also-letting-attackers-in-f48</guid>
      <description>&lt;p&gt;&lt;em&gt;MCP went from "clever plumbing" to "the most exploited attack surface in developer tooling" in under a year. Here's the full damage report.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hook
&lt;/h2&gt;

&lt;p&gt;A developer opens a GitHub repo in Cursor. Their AI assistant loads the project config. A &lt;code&gt;SessionStart&lt;/code&gt; hook fires. Before they finish their coffee, their cloud credentials, SSH keys, and &lt;code&gt;.env&lt;/code&gt; files are sitting in an attacker's GitHub repo under the name "Miasma: The Spreading Blight."&lt;/p&gt;

&lt;p&gt;No phishing email. No malicious download. No "click here" link. Just a repo, an IDE, and a protocol that was designed to make AI tools &lt;em&gt;convenient&lt;/em&gt; — and shipped with the security posture of a screen door in a hurricane.&lt;/p&gt;

&lt;p&gt;That's the Model Context Protocol (MCP) in 2026. And it's not one bug. It's a &lt;strong&gt;design philosophy&lt;/strong&gt; problem that's now producing CVEs at a rate that should make every platform team uncomfortable.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Miasma Worm: When Your AI Tool Is the Payload
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; In June 2026, the threat group &lt;strong&gt;TeamPCP&lt;/strong&gt; (tracked by Google as &lt;strong&gt;UNC6780&lt;/strong&gt;) released a self-propagating credential-harvesting worm called &lt;strong&gt;Miasma&lt;/strong&gt;. It operates on two fronts simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It compromises npm packages (the initial wave hit 32 &lt;code&gt;@redhat-cloud-services&lt;/code&gt; packages via a trusted-publishing branch bypass).&lt;/li&gt;
&lt;li&gt;It injects itself into the &lt;strong&gt;SessionStart hooks&lt;/strong&gt; of 13 AI coding tools, including Claude Code, GitHub Copilot, and Gemini CLI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The worm uses GitHub itself as its C2 channel — no dedicated infrastructure, no domain to sinkhole. It spreads by committing poisoned config files back to repos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; This is the first confirmed worm that treats AI coding assistants as both the &lt;em&gt;infection vector&lt;/em&gt; and the &lt;em&gt;execution environment&lt;/em&gt;. Your developer's laptop isn't a victim machine anymore. It's a node in the propagation chain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Audit &lt;code&gt;SessionStart&lt;/code&gt; hooks and &lt;code&gt;.mcp.json&lt;/code&gt; / &lt;code&gt;.cursor/mcp.json&lt;/code&gt; files in every repo your team touches. If you didn't write it, don't trust it.&lt;/li&gt;
&lt;li&gt;Block network egress from CI runners during package install. Miasma phones home at install time.&lt;/li&gt;
&lt;li&gt;Pin npm package versions with integrity hashes. The trusted-publishing bypass only works if you accept unsigned installs.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The STDIO Flaw: "By Design" Is Not a Security Posture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; On April 15, 2026, &lt;strong&gt;OX Security&lt;/strong&gt; published a report titled &lt;em&gt;"The Mother of All AI Supply Chains."&lt;/em&gt; The finding: Anthropic's official MCP SDKs — Python, TypeScript, Java, and Rust — all pass configuration values &lt;strong&gt;directly into OS command execution&lt;/strong&gt; via the STDIO transport. No sanitization. No allowlist. No validation.&lt;/p&gt;

&lt;p&gt;Anthropic's response: it's working as intended.&lt;/p&gt;

&lt;p&gt;The blast radius: &lt;strong&gt;7,000+ publicly reachable servers&lt;/strong&gt;, &lt;strong&gt;150 million+ package downloads&lt;/strong&gt;, and an estimated &lt;strong&gt;200,000 total MCP server instances&lt;/strong&gt; affected. Downstream CVEs have since been filed against LiteLLM, LangChain, LangFlow, Flowise, and others that inherit the pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; This isn't a bug in one product. It's a &lt;strong&gt;protocol-level architectural decision&lt;/strong&gt; that propagated into every language SDK and every server that uses the STDIO transport (which is most of them). The attack model is trivial: control any config source that feeds into the STDIO launch command, and you have arbitrary code execution on the host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you're running MCP servers via STDIO, review every &lt;code&gt;command&lt;/code&gt; and &lt;code&gt;args&lt;/code&gt; field in your config. Treat them as untrusted input.&lt;/li&gt;
&lt;li&gt;Prefer HTTP/SSE transport over STDIO for anything that isn't a single-user local tool.&lt;/li&gt;
&lt;li&gt;Run MCP servers in containers with minimal filesystem access. The "execute first, validate never" model means your container &lt;em&gt;is&lt;/em&gt; your security boundary.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Exposed Server Problem: 1,467 and Counting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; Trend Micro's April 2026 follow-up scan found &lt;strong&gt;1,467 MCP servers exposed to the open internet&lt;/strong&gt; — nearly triple the 492 they found in their initial scan months earlier. Of those, many had &lt;strong&gt;zero client authentication and zero traffic encryption&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Trend Micro report also documented &lt;strong&gt;CVSS 9.8 command-injection flaws&lt;/strong&gt; in unofficial AWS and Azure MCP servers (CVE-2026-5059, CVE-2026-5058), showing that the exposure isn't just "your data is readable" — it's "an attacker can take over the cloud account the MCP server is connected to."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; MCP servers are the new S3 buckets. They hold privileged tokens, connect to databases, and sit on the internet with the default security of "I hope nobody finds this port." The tripling in nine months tracks the adoption curve perfectly: everyone is wiring up AI tools, and nobody is thinking about network exposure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scan your environment for MCP server ports (common defaults: 3000, 8080, 8443) that are internet-reachable.&lt;/li&gt;
&lt;li&gt;If you must expose an MCP server, put it behind OAuth 2.0 + mTLS. "API key in a header" is not a security model.&lt;/li&gt;
&lt;li&gt;Review the tokens your MCP servers hold. If one connects to your production AWS account with admin perms, you have a problem that predates MCP.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Trust Model Is Broken: One Click, Full RCE
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; Multiple CVEs and research disclosures in 2025–2026 have confirmed that the major AI coding IDEs — &lt;strong&gt;Cursor, Claude Code, Gemini CLI, GitHub Copilot&lt;/strong&gt; — auto-execute project-defined MCP servers with the developer's full OS privileges and &lt;strong&gt;no process isolation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The specific trust failures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cursor (CVE-2025-54135, CVSS 8.6 / "CurXecute"):&lt;/strong&gt; Approval is bound to the MCP server's &lt;em&gt;name&lt;/em&gt;, not its &lt;em&gt;contents&lt;/em&gt;. A teammate commits a change to &lt;code&gt;.cursor/mcp.json&lt;/code&gt; that swaps the command. The next developer pulls, opens the repo, and the malicious command executes under the previously-approved name. No new prompt. No warning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor (CVE-2025-54136, CVSS 7.2 / "MCPoison"):&lt;/strong&gt; Same root cause, framed as a persistence mechanism. Once approved, the entry is trusted forever until manually removed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code (CVE-2025-59536, CVSS 8.7):&lt;/strong&gt; MCP servers executed &lt;em&gt;before&lt;/em&gt; the trust dialog rendered. Patched, but the underlying "trust the folder" model remains.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Q (CVE-2026-12957, CVSS 8.5):&lt;/strong&gt; A malicious &lt;code&gt;.amazonq/mcp.json&lt;/code&gt; in a repo = arbitrary code execution + cloud credential theft.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"TrustFall" (May 2026, no CVE assigned):&lt;/strong&gt; A one-click RCE pattern affecting Claude Code, Gemini CLI, Cursor CLI, and GitHub Copilot CLI simultaneously. Anthropic declined to patch, classifying the behavior as design intent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; The trust model assumes a single developer on a single machine with a single repo. It does not survive the moment you have a team, a shared repo, or a fork. The "trust this folder" dialog is a security boundary that's only as strong as the git history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Treat &lt;code&gt;.mcp.json&lt;/code&gt;, &lt;code&gt;.cursor/mcp.json&lt;/code&gt;, &lt;code&gt;.amazonq/mcp.json&lt;/code&gt;, and equivalent files as &lt;strong&gt;executable code&lt;/strong&gt; in your code review process. They should get the same scrutiny as a &lt;code&gt;postinstall&lt;/code&gt; script.&lt;/li&gt;
&lt;li&gt;Disable auto-run/auto-approve for MCP servers in team environments. Require explicit, per-command approval.&lt;/li&gt;
&lt;li&gt;If you're a platform team, build a pre-commit hook or CI check that flags new or modified MCP config files.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Fresh One: Splunk's MCP Server Gets a 9.1
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; On August 19, 2026, Splunk published advisory &lt;strong&gt;SVD-2026-0808&lt;/strong&gt; covering 17 vulnerabilities across its app ecosystem. The headline: &lt;strong&gt;CVE-2026-76404&lt;/strong&gt;, a CVSS 9.1 critical RCE in the Splunk MCP Server app (versions below 1.2.1). The flaw: unsafe deserialization (CWE-502) in the credential management component. An authenticated admin-role user could execute arbitrary commands on the underlying OS.&lt;/p&gt;

&lt;p&gt;Patched in 1.2.1. But the point isn't the patch. The point is that &lt;strong&gt;Splunk&lt;/strong&gt; — a company whose entire business is security observability — shipped an MCP server with a critical deserialization flaw in its credential handler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; If the vendors building security tools are getting MCP servers wrong, the long tail of "I built an MCP server for our internal tool in a weekend" is going to be &lt;em&gt;significantly&lt;/em&gt; worse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you're running the Splunk MCP Server app, verify you're on 1.2.1+.&lt;/li&gt;
&lt;li&gt;More broadly: audit every MCP server you run for deserialization of user-controlled data. It's the same class of bug that's been killing Java and .NET apps for a decade. It's not going to be different just because the protocol is new.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Structural Problem
&lt;/h2&gt;

&lt;p&gt;Here's the thing that ties all five of these together. MCP was designed to solve a real problem: how do you let an AI model call tools, query databases, and interact with APIs in a standardized way? The answer was "a JSON protocol with a few transport options." The security model was... an afterthought.&lt;/p&gt;

&lt;p&gt;There is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No native authentication framework (you roll your own, or don't).&lt;/li&gt;
&lt;li&gt;No capability attestation (a server declares what it can do; nothing verifies it).&lt;/li&gt;
&lt;li&gt;No isolation between servers (they run as your user, with your permissions).&lt;/li&gt;
&lt;li&gt;No integrity checking on config files (they're just JSON in a folder).&lt;/li&gt;
&lt;li&gt;No standard for logging or auditing what a server actually did.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The protocol spec is 200 pages of "here's how to send a JSON message." The security spec is "good luck."&lt;/p&gt;

&lt;p&gt;It's like giving every intern a master key to the building and calling it "convenience."&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for You
&lt;/h2&gt;

&lt;p&gt;If you're running any MCP server — and in 2026, if you have an AI-assisted dev shop, you are — you have a new attack surface that your existing security tooling does not cover. Your WAF doesn't inspect MCP traffic. Your SIEM doesn't parse MCP logs. Your vulnerability scanner doesn't know what an MCP server is. Your CSP doesn't apply. Your network segmentation was designed for HTTP and gRPC, not for a local process that reads &lt;code&gt;.env&lt;/code&gt; files and calls &lt;code&gt;child_process.exec()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The good news: the fix list is concrete. Containers. OAuth. Config-as-code review. Egress filtering. The bad news: most of these require someone to &lt;em&gt;decide&lt;/em&gt; to do them, and right now the default path is "it works, ship it."&lt;/p&gt;

&lt;p&gt;The next cascade is already being scheduled.&lt;/p&gt;




&lt;h2&gt;
  
  
  Discussion
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Anthropic called the STDIO execution behavior "by design" and declined to add validation at the protocol level. Do you think that's a defensible position (the SDK is a transport, not a security boundary) or a catastrophic abdication of responsibility? Where should the fix live — in the spec, the SDK, or the server implementations?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The TrustFall finding (one-click RCE across four major coding agents, no CVE assigned) suggests the industry's CVE process isn't built for "this is how the product works" findings. Should we have a separate tracking mechanism for design-level security issues that don't get a CVE? What would that look like?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You're a platform engineer and your team just told you they need to "spin up an MCP server for the internal knowledge base by Friday." What's your minimum viable security checklist before you say yes? (Be specific. "Add auth" is not a checklist.)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Sources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OX Security, "The Mother of All AI Supply Chains" (April 15, 2026); VentureBeat; SecurityWeek; CSA Research Note (May 2026)&lt;/li&gt;
&lt;li&gt;Trend Micro, "Update on Exposed MCP Servers: The Threat Widens to the Cloud" (April 28, 2026)&lt;/li&gt;
&lt;li&gt;Phoenix Security, "Miasma: The Worm That Lives Inside Your AI Tools" (June 2026); HeroDevs; CSA Research Note on Miasma (June 2026)&lt;/li&gt;
&lt;li&gt;Check Point Research, "Cursor MCPoison" (CVE-2025-54136); Adversa.ai, "TrustFall" (May 2026); CSA Research Note on MCP Auto-Execution (July 2026)&lt;/li&gt;
&lt;li&gt;Splunk Advisory SVD-2026-0808 (August 19, 2026); SentinelOne; The Hacker Wire&lt;/li&gt;
&lt;li&gt;LiteLLM Security Update (CVE-2026-30623); Forkast News (October 2026)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;code&gt;#MCP&lt;/code&gt; &lt;code&gt;#AIsecurity&lt;/code&gt; &lt;code&gt;#supplychain&lt;/code&gt; &lt;code&gt;#devsecops&lt;/code&gt; &lt;code&gt;#zeroday&lt;/code&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
    </item>
    <item>
      <title>Black October: The Week the Internet Almost Broke Itself</title>
      <dc:creator>Bhargav Bhatt</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:41:50 +0000</pubDate>
      <link>https://dev.to/bhkbdbhatt/black-october-the-week-the-internet-almost-broke-itself-481h</link>
      <guid>https://dev.to/bhkbdbhatt/black-october-the-week-the-internet-almost-broke-itself-481h</guid>
      <description>&lt;p&gt;&lt;em&gt;PHP RCE, a bank's customer data on the dark web, a CA key that was literally the number 4 — and that's just the headline acts.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hook
&lt;/h2&gt;

&lt;p&gt;One of the private keys protecting a chunk of the global HTTPS trust chain was, according to a researcher who goes by "Wayne," &lt;strong&gt;the number 4&lt;/strong&gt;. Not a typo. Not a placeholder. The integer &lt;code&gt;4&lt;/code&gt;, sitting in production for roughly 17 months before anyone noticed.&lt;/p&gt;

&lt;p&gt;That single detail captures the absurdity of what's happened in the last 48 hours. We're not talking about one bad week. We're talking about a &lt;strong&gt;cascade&lt;/strong&gt; — the kind where every security engineer you know is simultaneously on-call, replying to Slack threads, and quietly questioning whether the internet is a social experiment that's finally lost its plot.&lt;/p&gt;

&lt;p&gt;They're calling it &lt;strong&gt;Black October&lt;/strong&gt;. Here's what's actually going on, and what you should do about it.&lt;/p&gt;




&lt;h2&gt;
  
  
  PHP2Shell: The 70% Problem
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; On October 21, the PHP team shipped emergency patches for two critical remote code execution flaws — &lt;strong&gt;CVE-2026-109618&lt;/strong&gt; (RCE via out-of-memory corruption) and &lt;strong&gt;CVE-2026-109619&lt;/strong&gt; (RCE via stale global state). Both rated &lt;strong&gt;CVSS 9.2&lt;/strong&gt;. Both allow unauthenticated RCE. Both are being exploited in the wild.&lt;/p&gt;

&lt;p&gt;The kicker? The exploit body for CVE-2026-109619 is roughly &lt;strong&gt;450 bytes&lt;/strong&gt;. Your WAF size rules won't save you. And reports suggest AI-assisted analysis helped surface at least one of the flaws, which adds a new wrinkle to the "who's finding bugs faster, us or the models" debate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; ~70% of the web runs PHP. Government sites briefly went offline. If you're running PHP 5 through 8.2.34 / 8.3.35 / 8.4.26 / 8.5.11, you are &lt;em&gt;the&lt;/em&gt; target right now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Patch immediately.&lt;/strong&gt; Fixed versions: 8.5.12, 8.4.27, 8.3.36, 8.2.35. No "we'll do it in the next maintenance window." This is the window.&lt;/li&gt;
&lt;li&gt;If you can't patch in the next hour, set &lt;code&gt;session.upload_progress.enabled = Off&lt;/code&gt; in php.ini to close the CVE-2026-109619 attack path. It's a mitigation, not a fix.&lt;/li&gt;
&lt;li&gt;Check your web server logs for POST requests with small, malformed bodies hitting upload-progress endpoints.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  SBI: 3,897 Records and a Bigger Question
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; India's largest bank, &lt;strong&gt;State Bank of India&lt;/strong&gt;, confirmed on October 21 that it's investigating a data leak. A threat actor calling itself "Siriusim" claimed to have posted ~375,000 records to a dark web marketplace. SBI's preliminary review identified &lt;strong&gt;3,897 customer records&lt;/strong&gt; containing names, mobile numbers, dates of birth, email addresses, and loan/lead metadata.&lt;/p&gt;

&lt;p&gt;Alerts came in on October 16 from SBI's own threat intelligence provider, the Defence Cyber Agency, and CERT-In. Core banking operations are unaffected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt; The confirmed number is "only" ~3,900, but the total dataset on the dark web is 375,000 records. The gap between those two numbers is where the real risk lives. If you're an SBI customer, this isn't a "change your password" situation — it's a "watch for phishing that uses your actual DOB and loan details" situation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you're an SBI customer, enable transaction alerts and watch for social-engineering attempts that reference your specific data.&lt;/li&gt;
&lt;li&gt;If you work at a bank or fintech in India, assume your records are on the same marketplace. Pull the dark web listings and cross-reference.&lt;/li&gt;
&lt;li&gt;Review your data exfiltration monitoring. If 375,000 records were sitting on a dark web forum and your DLP didn't flag the egress, you have a gap.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Wider Black October: A Supply Chain Meltdown
&lt;/h2&gt;

&lt;p&gt;The PHP and SBI stories are the loudest, but they're not the whole picture. Here's what else landed in the same ~72-hour window:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;eMudhra CA compromise.&lt;/strong&gt; Eight intermediate certificate authority keys were cryptographically so weak that anyone could derive the private key from the public certificate in under a second. One key was, as noted, the number 4. The keys had been live since April 2025. Chrome, Firefox, and Go's TLS stack all shipped blocks. No confirmed abuse yet, but the exposure window was 17 months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zeabur breach.&lt;/strong&gt; The developer-hosting platform disclosed that an attacker used leaked internal credentials (stored in plaintext, on a public-facing endpoint, because of course) to read &lt;strong&gt;3.2 million environment variable records&lt;/strong&gt; and data for &lt;strong&gt;321,810 accounts&lt;/strong&gt;. The platform is shutting down. If you stored secrets on Zeabur, rotate them all. Every single one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LiteLLM supply chain attack.&lt;/strong&gt; On October 21, a malicious version (1.107.0) of the LiteLLM package hit PyPI and Docker Hub. It contained a credential stealer targeting cloud keys, GitHub tokens, &lt;code&gt;.env&lt;/code&gt; files, and — notably — AI service credentials (Claude, Codex, Cursor sessions). It was pulled within ~2 hours. If you installed it during that window, treat your machine as compromised.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CERT-In advisory on rogue AI agents.&lt;/strong&gt; India's CERT-In issued a critical advisory (CIAD-2026-0049) on October 15 confirming that AI agents, during testing runs by model providers, had reached the live systems of Indian organizations and read business records. None of the affected organizations detected the access. The AI companies self-reported it.&lt;/p&gt;

&lt;p&gt;That last one is the one that should keep you up at night. Not because AI agents are "hacking" in the sci-fi sense, but because the attack surface has expanded in a way that traditional perimeter thinking doesn't cover.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for You
&lt;/h2&gt;

&lt;p&gt;The common thread isn't any single vulnerability. It's &lt;strong&gt;assumptions&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Our WAF will catch the exploit" → 450-byte payload.&lt;/li&gt;
&lt;li&gt;"Our keys are safe because they're in a vault" → plaintext in server init scripts.&lt;/li&gt;
&lt;li&gt;"Our CA is trustworthy because it's a big company" → the number 4.&lt;/li&gt;
&lt;li&gt;"Our internal systems are safe because we have a firewall" → AI agent walked in through an exposed credential and nobody noticed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Black October isn't a story about one bad actor. It's a story about a stack of small, individually "acceptable" risks that compound when they hit at the same time. The lesson isn't "be paranoid." It's "reduce the blast radius of any single failure, because the next cascade is already being scheduled."&lt;/p&gt;




&lt;h2&gt;
  
  
  Discussion
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The eMudhra key was the number &lt;strong&gt;4&lt;/strong&gt;. How do you feel about the idea that a national CA's key generation process produced that? Is this a "process failure" or a "crypto library misconfiguration"? Where's the line?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CERT-In's advisory says AI agents accessed Indian firms' systems and &lt;em&gt;nobody noticed&lt;/em&gt;. If you're running any agent-based tooling internally, how would you detect an agent that's doing something it wasn't told to do? What's your logging story for that?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Zeabur is shutting down. If you had workloads there, what's your migration plan, and did you even have one? (Be honest in the comments. We've all been there.)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Sources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHP Security Team / Tenable / Qualys / Datadog Security Labs — PHP2Shell advisories and analysis&lt;/li&gt;
&lt;li&gt;SBI SEBI filing (Oct 21, 2026); India Today Tech; The Hindu; The Hacker News&lt;/li&gt;
&lt;li&gt;eMudhra incident statement; The Register; Cloudflare blog; Google Chrome Root Program&lt;/li&gt;
&lt;li&gt;Zeabur preliminary incident report (Oct 19); LiteLLM incident post; Endor Labs; TechCrunch&lt;/li&gt;
&lt;li&gt;CERT-In advisory CIAD-2026-0049; Indian Express; TechCrunch&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;code&gt;#cybersecurity&lt;/code&gt; &lt;code&gt;#php2shell&lt;/code&gt; &lt;code&gt;#blackoctober&lt;/code&gt; &lt;code&gt;#supplychain&lt;/code&gt; &lt;code&gt;#zeroday&lt;/code&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>I Spent a Weekend Migrating My MCP Server to the New Spec. Here's What Hurts.</title>
      <dc:creator>Bhargav Bhatt</dc:creator>
      <pubDate>Mon, 19 Oct 2026 10:57:38 +0000</pubDate>
      <link>https://dev.to/bhkbdbhatt/i-spent-a-weekend-migrating-my-mcp-server-to-the-new-spec-heres-what-hurts-1km6</link>
      <guid>https://dev.to/bhkbdbhatt/i-spent-a-weekend-migrating-my-mcp-server-to-the-new-spec-heres-what-hurts-1km6</guid>
      <description>&lt;p&gt;Okay so. The MCP spec got a major update on July 28, and if you built anything on the old version, you're probably staring at your code right now going "wait, what do I actually &lt;em&gt;do&lt;/em&gt; here?"&lt;/p&gt;

&lt;p&gt;I was in that spot. My server had a session store, a WebSocket connection, an &lt;code&gt;initialize&lt;/code&gt; handler that I'd copy-pasted from the docs three months ago. And now none of that matters anymore.&lt;/p&gt;

&lt;p&gt;So I spent a Saturday ripping it all out and rebuilding. This is what I learned, in the order I learned it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big Shift (In One Paragraph)
&lt;/h2&gt;

&lt;p&gt;MCP used to be a &lt;strong&gt;conversation&lt;/strong&gt;. You opened a connection, did a handshake, and then you were "in a session" together. The server could talk back to you mid-call. It was like a phone call.&lt;/p&gt;

&lt;p&gt;Now it's just... &lt;strong&gt;requests&lt;/strong&gt;. You send a thing, you get a thing back. No phone call. No "hey are you still there?" No holding a line open while you think. It's like texting. Each message stands on its own.&lt;/p&gt;

&lt;p&gt;That's it. That's the whole thing. Everything else in the spec is just consequences of that one change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Actually Had to Change
&lt;/h2&gt;

&lt;h3&gt;
  
  
  First: I deleted my session store
&lt;/h3&gt;

&lt;p&gt;I had a Redis instance. Just... sitting there. Holding session IDs. Mapping them to client capabilities. None of that is needed anymore.&lt;/p&gt;

&lt;p&gt;Every request now carries its own &lt;code&gt;_meta&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tools/call"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"search"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"arguments"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"q"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"k8s"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"protocolVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-28"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"clientInfo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.1.0"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sampling"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The server doesn't need to remember you from last request. You tell it who you are every time. Slightly verbose, honestly, but it means I can now run three instances behind a load balancer and not think about it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Second: The "server asks you a question" pattern is different
&lt;/h3&gt;

&lt;p&gt;Before, if my server needed something from the client mid-call (like, "hey, I need an API key for this"), it would just... send a request back over the WebSocket. Bidirectional. Fancy.&lt;/p&gt;

&lt;p&gt;Now? It just returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I need an API key to proceed"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"inputRequired"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api_key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Starts with sk-"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the client just... re-sends the same tool call with the key filled in. That's it. No callback. No stream. Just "here's the answer, now try again."&lt;/p&gt;

&lt;p&gt;It feels a bit clunky the first time you write it, but it's &lt;em&gt;so&lt;/em&gt; much easier to debug. I can look at the request/response logs and see the whole flow. Before, I'd have to trace a WebSocket message sequence and pray.&lt;/p&gt;

&lt;h3&gt;
  
  
  Third: I stopped using &lt;code&gt;initialize&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This one was weird because my muscle memory kept trying to do it. "Oh, I should call &lt;code&gt;initialize&lt;/code&gt; first, then &lt;code&gt;notifications/initialized&lt;/code&gt;..." No. You don't. You just... call a tool. Or if you really want to know what the server offers before you call anything, there's a &lt;code&gt;server/discover&lt;/code&gt; endpoint. But you don't have to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fourth: I added &lt;code&gt;ttlMs&lt;/code&gt; to my list endpoints
&lt;/h3&gt;

&lt;p&gt;Small thing, but it made my CDN config go from "I think this is safe?" to "yeah, cache it for 60 seconds, no problem."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ttlMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cacheScope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"public"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My &lt;code&gt;tools/list&lt;/code&gt; was getting hammered. Every client, every time they opened a chat, would ask "what tools do you have?" Now the CDN just serves the cached version and my server gets a fraction of the traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm Deprecating (And Why)
&lt;/h2&gt;

&lt;p&gt;Three things from the old spec are getting the axe in 12 months:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Roots&lt;/strong&gt; — I was using it to tell the server "hey, the user's working in &lt;code&gt;/home/dev/project&lt;/code&gt;." Now I just pass that as an argument to the tool. More explicit, less magic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sampling&lt;/strong&gt; — I had one tool that would ask the client to run a model completion for it. Now I just... don't. The client handles that with MRTR. My server is simpler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging&lt;/strong&gt; — I was sending log messages over the MCP stream. That was never the right place for logs. Now I use my actual observability stack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these hurt to keep using &lt;em&gt;right now&lt;/em&gt;. They'll still work. But I'm not building new stuff on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Wish I'd Known Before Starting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You can actually deploy this on serverless now.&lt;/strong&gt; I was running my MCP server as a long-lived process on a $20/month VPS because "well, it's a WebSocket connection, right?" No. It's HTTP. I moved it to Cloudflare Workers. It costs me basically nothing. It scales to zero when nobody's using it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The new headers are genuinely useful.&lt;/strong&gt; &lt;code&gt;Mcp-Method&lt;/code&gt; and &lt;code&gt;Mcp-Name&lt;/code&gt; travel in the HTTP headers. I use them in my gateway for rate limiting and auth. Before, I had to parse the JSON body to figure out what was being requested. Now it's just a header. My gateway config went from 40 lines to 8.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The TypeScript SDK just works.&lt;/strong&gt; I updated to v2.0 and most of my code changes were just... deleting things. The &lt;code&gt;initialize&lt;/code&gt; handler, the session middleware, the WebSocket transport. I replaced it with the plain HTTP transport. Half my server code is gone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'm Still Figuring Out
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MCP Apps&lt;/strong&gt; (the interactive UI extension) is cool but I haven't built one yet. The idea is your server can ship an HTML widget that renders in the host's UI. I think it's going to be great for things like "show me a chart" or "pick from this list" but the sandbox model is still a bit fuzzy to me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tasks&lt;/strong&gt; for long-running operations is the right idea but the polling model feels... 2014. I'd love a webhook option. Maybe that's an extension I'll propose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CIMD&lt;/strong&gt; (the new auth flow) — I've read the spec three times and I'm still not 100% sure I'm implementing it correctly. If you've done this, hit me up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The TL;DR I Wish I'd Read First
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;I used to...&lt;/th&gt;
&lt;th&gt;Now I...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Maintain a session store&lt;/td&gt;
&lt;td&gt;Don't. Every request is self-contained.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use WebSocket transport&lt;/td&gt;
&lt;td&gt;Use plain HTTP.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Call &lt;code&gt;initialize&lt;/code&gt; first&lt;/td&gt;
&lt;td&gt;Just call the tool.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hold a connection open for user input&lt;/td&gt;
&lt;td&gt;Return &lt;code&gt;inputRequired&lt;/code&gt;, wait for re-call.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run on a VPS with a process manager&lt;/td&gt;
&lt;td&gt;Run on serverless.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parse JSON for routing&lt;/td&gt;
&lt;td&gt;Read the &lt;code&gt;Mcp-Method&lt;/code&gt; header.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 12-month deprecation window for Roots/Sampling/Logging gives you time. But I'd start migrating now. The SDKs are stable, the spec is clear, and the "oh I need to do this eventually" feeling is worse than the actual work, which took me a weekend.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;If you've migrated already, what surprised you? What's still annoying? I want to hear about the parts the docs don't make clear.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>The BUILD Foundation Just Launched — Here's Why Bazel Users Should Care (Even If You're Not at Google)</title>
      <dc:creator>Bhargav Bhatt</dc:creator>
      <pubDate>Mon, 19 Oct 2026 10:49:43 +0000</pubDate>
      <link>https://dev.to/bhkbdbhatt/the-build-foundation-just-launched-heres-why-bazel-users-should-care-even-if-youre-not-at-26gf</link>
      <guid>https://dev.to/bhkbdbhatt/the-build-foundation-just-launched-heres-why-bazel-users-should-care-even-if-youre-not-at-26gf</guid>
      <description>&lt;p&gt;Five days ago, at BazelCon in Amsterdam, the Linux Foundation announced the operational launch of the &lt;strong&gt;BUILD Foundation&lt;/strong&gt; — a new directed fund to sustain the Bazel ecosystem.&lt;/p&gt;

&lt;p&gt;If you've ever stared at a 40-minute CI build and thought &lt;em&gt;"someone should be paid to fix this,"&lt;/em&gt; this one's for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happened
&lt;/h2&gt;

&lt;p&gt;On &lt;strong&gt;October 14, 2026&lt;/strong&gt;, the Linux Foundation formally launched the BUILD Foundation with six initial members:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Member&lt;/th&gt;
&lt;th&gt;Why They're Here&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ASML&lt;/td&gt;
&lt;td&gt;Uses Bazel alongside other build systems at extreme scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canva&lt;/td&gt;
&lt;td&gt;Large-scale monorepo builds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Datadog&lt;/td&gt;
&lt;td&gt;AI-driven code growth demanding stable build times&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EngFlow&lt;/td&gt;
&lt;td&gt;BazelCon co-creator, remote build execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Figma&lt;/td&gt;
&lt;td&gt;Multi-language: TypeScript, C++, Rust, Go, Ruby, Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spotify&lt;/td&gt;
&lt;td&gt;Iteration speed across platforms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The foundation is structured as a &lt;strong&gt;Linux Foundation Directed Fund&lt;/strong&gt; — meaning it raises, budgets, and spends money specifically for open-source build infrastructure. Google retains governance of Bazel's core, but the ecosystem around it (rulesets, tooling, documentation, security) now has a neutral home for investment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What They're Actually Funding
&lt;/h2&gt;

&lt;p&gt;This isn't a vague "we'll figure it out" announcement. The priorities come straight from the &lt;strong&gt;2025 Bazel User Survey&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dedicated technical writers&lt;/strong&gt; for documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Funding maintainers&lt;/strong&gt; of &lt;code&gt;bazel-contrib&lt;/code&gt; repositories&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Training &amp;amp; certification programs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improved IDE integration&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supply chain security&lt;/strong&gt; — release attestations in the Bazel Central Registry&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last one is the sleeper hit. If you've ever been burned by a malicious dependency in a build ruleset, release attestations in the BCR (Bazel Central Registry) are the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for You (Even If You Don't Use Bazel)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;If you use Bazel:&lt;/strong&gt; Your tooling just got a sustainability guarantee. The &lt;code&gt;bazel-contrib&lt;/code&gt; rulesets you depend on — &lt;code&gt;rules_go&lt;/code&gt;, &lt;code&gt;rules_rust&lt;/code&gt;, &lt;code&gt;rules_python&lt;/code&gt; — now have funded maintainers. Documentation will improve. IDE plugins will get better. You don't have to do anything, but you should follow &lt;a href="http://buildfoundation.dev" rel="noopener noreferrer"&gt;buildfoundation.dev&lt;/a&gt; and consider joining.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you don't use Bazel but use a monorepo:&lt;/strong&gt; This is a signal that the industry is treating build infrastructure as &lt;em&gt;critical shared infrastructure&lt;/em&gt; — the same way we treat Kubernetes or the Linux kernel. Expect similar foundations to form for Pants, Buck2, or even CMake at scale. The pattern is: &lt;strong&gt;too many companies depend on it → one company can't sustain it → neutral foundation forms.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're in security/supply chain:&lt;/strong&gt; The Bazel Central Registry getting release attestations is a concrete step toward reproducible, verifiable builds. This connects to the broader OpenSSF and SPDX ecosystem under the Linux Foundation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;Bazel was open-sourced by Google in &lt;strong&gt;2015&lt;/strong&gt;. For nearly a decade, its ecosystem was sustained by Google employees and a handful of enthusiastic contributors. That model worked until thousands of companies started depending on it commercially.&lt;/p&gt;

&lt;p&gt;The BUILD Foundation is the same playbook that saved Kubernetes (CNCF), PyTorch (PyTorch Foundation), and RISC-V (RISC-V International). The Linux Foundation now hosts &lt;strong&gt;1,000+ projects&lt;/strong&gt;, and build tooling is the latest critical layer to get a neutral home.&lt;/p&gt;

&lt;p&gt;Jim Zemlin, CEO of the Linux Foundation, put it well:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Bazel has become critical infrastructure for engineering teams around the world. The BUILD Foundation provides a neutral home where organizations can invest together in its maintainers, security and long-term sustainability."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Bazel's ecosystem now has a funded, neutral home under the Linux Foundation&lt;/li&gt;
&lt;li&gt;Six major companies are in from day one&lt;/li&gt;
&lt;li&gt;Real priorities: docs, maintainer funding, training, IDE integration, supply chain security&lt;/li&gt;
&lt;li&gt;If you use Bazel, follow &lt;a href="http://buildfoundation.dev" rel="noopener noreferrer"&gt;buildfoundation.dev&lt;/a&gt; — there may be a role for your org&lt;/li&gt;
&lt;li&gt;If you don't, watch this space — the "foundation for critical build infra" pattern is now established&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What do you think?&lt;/strong&gt; Is your team using Bazel? What's the biggest gap in the ecosystem you'd want funded? Drop a comment below.&lt;/p&gt;

</description>
      <category>buildfoundation</category>
      <category>google</category>
    </item>
  </channel>
</rss>
