<?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: Subodh Kc</title>
    <description>The latest articles on DEV Community by Subodh Kc (@subodhkc).</description>
    <link>https://dev.to/subodhkc</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%2F4040708%2Fbb695082-c5c3-4970-95c0-51973e931427.png</url>
      <title>DEV Community: Subodh Kc</title>
      <link>https://dev.to/subodhkc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/subodhkc"/>
    <language>en</language>
    <item>
      <title>Where Should Your Company Use AI? A Decision Framework</title>
      <dc:creator>Subodh Kc</dc:creator>
      <pubDate>Thu, 15 Oct 2026 15:18:03 +0000</pubDate>
      <link>https://dev.to/subodhkc/where-should-your-company-use-ai-a-decision-framework-19a6</link>
      <guid>https://dev.to/subodhkc/where-should-your-company-use-ai-a-decision-framework-19a6</guid>
      <description>&lt;p&gt;A team can fill a whiteboard with AI ideas in an hour.&lt;/p&gt;

&lt;p&gt;Customer service chatbot. Sales assistant. Internal knowledge bot. Meeting summaries. Automated reporting. Proposal generation. An agent that handles part of operations.&lt;/p&gt;

&lt;p&gt;The list is easy.&lt;/p&gt;

&lt;p&gt;The decision is not.&lt;/p&gt;

&lt;p&gt;Most of those ideas start with the technology and work backward until someone finds a problem that seems to fit. That can produce an interesting demo. It does not tell us whether anything important in the business should actually change.&lt;/p&gt;

&lt;p&gt;I prefer to start somewhere else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The first AI question is not what can be automated. It is what should change.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That sounds like a small distinction. In practice, it changes the entire investigation.&lt;/p&gt;

&lt;p&gt;Instead of searching the company for places to install AI, start by looking for operating pressure. Where are customers waiting? Where does skilled capacity disappear into preparation and coordination? Where does information exist but fail to arrive when a decision has to be made? Where does work lose context at a handoff? What useful capability is not happening at all because it has historically been too expensive?&lt;/p&gt;

&lt;p&gt;Only after that do I ask whether AI belongs there.&lt;/p&gt;

&lt;p&gt;And even then, "AI can do it" is not enough.&lt;/p&gt;

&lt;p&gt;A worthwhile opportunity has to survive a harder test: it has to create a meaningful change, beat the simpler alternatives, fit the nature of the work, respect the right authority boundaries, produce evidence we can measure, and become a system somebody can actually operate.&lt;/p&gt;

&lt;p&gt;That is the difference between collecting AI ideas and making an AI decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the business, not the model
&lt;/h2&gt;

&lt;p&gt;When someone asks where a company should use AI, I do not think the best starting point is a catalog of use cases.&lt;/p&gt;

&lt;p&gt;A better investigation begins with the operating system of the business.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customer and Employee Wait States
&lt;/h3&gt;

&lt;p&gt;A customer may be waiting for an answer. An employee may be waiting for information. A manager may be waiting for analysis. A deal may be waiting for the one person who knows how to interpret an issue.&lt;/p&gt;

&lt;p&gt;Waiting is useful because it exposes friction without presuming a solution.&lt;/p&gt;

&lt;p&gt;The question is not yet whether a model can answer faster. The question is why the work is waiting and what would change if it stopped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Capacity Lost to Preparation
&lt;/h3&gt;

&lt;p&gt;Repetitive work is the obvious version, but it is not the only one.&lt;/p&gt;

&lt;p&gt;A highly paid employee may spend hours gathering information before using five minutes of actual judgment. A technical specialist may repeatedly explain the same context to different teams. A manager may spend more time preparing the decision than making it.&lt;/p&gt;

&lt;p&gt;The opportunity may not be "replace the task." It may be to remove the preparation that keeps a person from doing the part of the job that actually requires that person.&lt;/p&gt;

&lt;h3&gt;
  
  
  Information Trapped Across Systems
&lt;/h3&gt;

&lt;p&gt;Companies often have more information than they can use.&lt;/p&gt;

&lt;p&gt;It sits across documents, conversations, tickets, emails, systems and individual experience. The constraint is not necessarily a lack of data. It can be the inability to bring the right evidence together when somebody needs to act.&lt;/p&gt;

&lt;p&gt;That is a very different problem from "we need an internal chatbot."&lt;/p&gt;

&lt;h3&gt;
  
  
  Expensive Handoffs Between Departments
&lt;/h3&gt;

&lt;p&gt;A process can work well inside each department and still fail between them.&lt;/p&gt;

&lt;p&gt;Context gets lost. Someone re-enters information. Another person has to interpret what the first person meant. Work stops while ownership changes.&lt;/p&gt;

&lt;p&gt;A handoff can be a technology problem. It can also be a process problem, an ownership problem, or an information design problem. We should know which one before introducing AI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quality Variation Beyond Judgment
&lt;/h3&gt;

&lt;p&gt;Two people receive the same information and produce very different outcomes.&lt;/p&gt;

&lt;p&gt;Sometimes that variation represents legitimate judgment. Sometimes it exposes a process that depends too heavily on memory, individual experience or inconsistent access to information.&lt;/p&gt;

&lt;p&gt;AI may help. A clearer standard may help more.&lt;/p&gt;

&lt;p&gt;The variation is the signal. AI is only one possible intervention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expertise as a Bottleneck
&lt;/h3&gt;

&lt;p&gt;Some work stops because the next step depends on a specialist.&lt;/p&gt;

&lt;p&gt;That might be a lawyer, engineer, analyst, senior operator, product expert, sales leader or simply the one person who knows how a system really works.&lt;/p&gt;

&lt;p&gt;The opportunity is not automatically to automate the expert. It may be to let more of the organization arrive at the expert with the evidence already gathered, routine questions already resolved, and the real judgment call clearly isolated.&lt;/p&gt;

&lt;p&gt;That protects expertise instead of pretending it is unnecessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Capabilities That Were Not Economical Before
&lt;/h3&gt;

&lt;p&gt;This is the category I find more interesting than repetitive work.&lt;/p&gt;

&lt;p&gt;A company may want every inbound interaction answered, every account researched, every document reviewed, every anomaly inspected, every proposal adapted, or every employee to have access to specialist-level assistance.&lt;/p&gt;

&lt;p&gt;Historically, the economics may have made that impossible.&lt;/p&gt;

&lt;p&gt;AI can change that boundary.&lt;/p&gt;

&lt;p&gt;That is different from automation. It is not only doing existing work cheaper. It can make a previously unavailable capability possible.&lt;/p&gt;

&lt;p&gt;This is where some of the more valuable opportunities hide because nobody is complaining about a broken workflow. The workflow does not exist yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn the pressure into a change
&lt;/h2&gt;

&lt;p&gt;Once an operating pressure appears, I still do not call it an AI use case.&lt;/p&gt;

&lt;p&gt;I try to describe the change first.&lt;/p&gt;

&lt;p&gt;"Build a customer service bot" is a technology idea.&lt;/p&gt;

&lt;p&gt;"Reduce the number of customers who wait until the next business day for a basic answer" is a business change.&lt;/p&gt;

&lt;p&gt;"Use AI for reporting" is a technology idea.&lt;/p&gt;

&lt;p&gt;"Give an operating leader the evidence needed for a weekly decision without spending six hours assembling it" is a business change.&lt;/p&gt;

&lt;p&gt;"Create a knowledge assistant" is a technology idea.&lt;/p&gt;

&lt;p&gt;"Reduce how often work stops because only one experienced employee knows where the answer is" is a business change.&lt;/p&gt;

&lt;p&gt;The second version gives us something important that the first does not.&lt;/p&gt;

&lt;p&gt;It gives us something to investigate and eventually measure.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Opportunity Card
&lt;/h3&gt;

&lt;p&gt;Before discussing models, vendors or architecture, capture the opportunity in a simple record:&lt;br&gt;
QuestionWhat we need to knowWhat is happening today?The current workflow or operating conditionWho experiences the problem?Customer, employee, operator, manager, partnerHow often does it happen?Frequency and volumeWhat is the current baseline?Time, cost, quality, delay, capacity, variation or errorWhat should change?The desired operating outcomeWhy does the change matter?Revenue, capacity, quality, experience, risk or a new capabilityWho owns the outcome?The person accountable if it improves or does notWhat evidence is missing?Assumptions we have not proved yet&lt;br&gt;
Notice what is absent.&lt;/p&gt;

&lt;p&gt;There is no model.&lt;/p&gt;

&lt;p&gt;There is no vendor.&lt;/p&gt;

&lt;p&gt;There is no architecture.&lt;/p&gt;

&lt;p&gt;There is not even an assumption that AI is the answer.&lt;/p&gt;

&lt;p&gt;That is intentional.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI has to beat the simpler intervention
&lt;/h2&gt;

&lt;p&gt;One of the easiest mistakes in an AI program is comparing AI with the status quo.&lt;/p&gt;

&lt;p&gt;That is the wrong comparison.&lt;/p&gt;

&lt;p&gt;If the current process is painful and an AI prototype looks better, the project can appear compelling.&lt;/p&gt;

&lt;p&gt;But perhaps the real alternatives are a better form, a cleaner process, an integration between two systems, a deterministic rule, ordinary software automation, or simply removing a step that should not exist.&lt;/p&gt;

&lt;p&gt;So the first gate I apply is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can a simpler intervention solve enough of the problem?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If it can, AI has to demonstrate why the additional complexity is justified.&lt;/p&gt;

&lt;p&gt;This matters because every AI system introduces another operating surface.&lt;/p&gt;

&lt;p&gt;Something has to provide context. Access has to be controlled. Outputs have to be evaluated. Failure has to be handled. Costs change with usage. Models and vendors change. Somebody has to decide what happens when the system is uncertain or wrong.&lt;/p&gt;

&lt;p&gt;That does not mean AI should be avoided.&lt;/p&gt;

&lt;p&gt;It means complexity has to earn its way into the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is the work actually shaped for AI?
&lt;/h2&gt;

&lt;p&gt;If the opportunity survives the simpler-alternative test, then I look at the nature of the work.&lt;/p&gt;

&lt;p&gt;Some problems are well suited to current AI capabilities because they involve messy language, interpretation, classification, synthesis, generation, pattern recognition, variable inputs or decisions where useful evidence is distributed across a lot of information.&lt;/p&gt;

&lt;p&gt;Other problems are better expressed as deterministic software.&lt;/p&gt;

&lt;p&gt;If the correct behavior can be written as:&lt;/p&gt;

&lt;p&gt;If X happens, always do Y.&lt;/p&gt;

&lt;p&gt;then an AI model may be unnecessary.&lt;/p&gt;

&lt;p&gt;If the work is closer to:&lt;/p&gt;

&lt;p&gt;Understand what this person is asking, retrieve the relevant context, apply several constraints, produce a useful response, know when confidence is inadequate, and escalate.&lt;/p&gt;

&lt;p&gt;then the AI case becomes more interesting.&lt;/p&gt;

&lt;p&gt;The distinction matters because "AI" is not one capability.&lt;/p&gt;

&lt;p&gt;A useful AI system can include models, deterministic rules, retrieval, APIs, permissions, workflow logic, human approvals and conventional software.&lt;/p&gt;

&lt;p&gt;The goal is not to find a place for a model.&lt;/p&gt;

&lt;p&gt;The goal is to design the smallest system capable of producing the desired change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then ask what the AI is allowed to do
&lt;/h2&gt;

&lt;p&gt;This is where many use-case frameworks become too shallow.&lt;/p&gt;

&lt;p&gt;They ask whether an idea has value and whether it is feasible.&lt;/p&gt;

&lt;p&gt;I think there is another question that matters just as much:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What authority are we giving the system?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An AI system that drafts something for a person to review is not the same system as one that communicates directly with a customer.&lt;/p&gt;

&lt;p&gt;A system that recommends a refund is different from one that issues it.&lt;/p&gt;

&lt;p&gt;A system that finds information is different from one that modifies the source.&lt;/p&gt;

&lt;p&gt;A system that proposes an appointment is different from one that changes a calendar.&lt;/p&gt;

&lt;p&gt;A system that summarizes a decision is different from one that makes it.&lt;/p&gt;

&lt;p&gt;The technology may look similar. The operating consequences are not.&lt;/p&gt;

&lt;p&gt;For any meaningful use case, I want to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What can the AI see?&lt;/li&gt;
&lt;li&gt;What can it infer?&lt;/li&gt;
&lt;li&gt;What can it say?&lt;/li&gt;
&lt;li&gt;What systems can it reach?&lt;/li&gt;
&lt;li&gt;What can it change?&lt;/li&gt;
&lt;li&gt;What can it trigger?&lt;/li&gt;
&lt;li&gt;What requires human approval?&lt;/li&gt;
&lt;li&gt;Who is responsible when an action crosses that boundary?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one place where opportunity discovery intersects with work I have done on &lt;a href="https://subodhkc.com/cognitive-systems-management" rel="noopener noreferrer"&gt;Cognitive Systems Management&lt;/a&gt; and the &lt;a href="https://subodhkc.com/packages/isaf" rel="noopener noreferrer"&gt;Instruction Stack Audit Framework&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The point is not to turn every opportunity conversation into a governance exercise.&lt;/p&gt;

&lt;p&gt;It is to recognize that an AI capability eventually becomes part of a larger system of instructions, data, permissions, tools, people and decisions.&lt;/p&gt;

&lt;p&gt;In ISAF, I use a similar principle for accountability: if behavior matters, we should be able to trace where relevant instructions entered the system and who owns the layer that influenced the outcome.&lt;/p&gt;

&lt;p&gt;At the opportunity stage, the executive version of that question is simpler:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If we give this system authority, will we still understand who controls what?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If we cannot answer that, we do not yet understand the system we are proposing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure tells us more than the happy path
&lt;/h2&gt;

&lt;p&gt;I also want to know what happens when the system is wrong.&lt;/p&gt;

&lt;p&gt;Not because every AI application is dangerous.&lt;/p&gt;

&lt;p&gt;Because failure exposes the real architecture.&lt;/p&gt;

&lt;p&gt;Consider a system answering a basic internal policy question.&lt;/p&gt;

&lt;p&gt;A wrong answer might create minor confusion and be corrected quickly.&lt;/p&gt;

&lt;p&gt;Now consider a similar model communicating contractual information to a customer, making a hiring recommendation, approving a financial action or controlling part of an operational process.&lt;/p&gt;

&lt;p&gt;The probability of an incorrect output might be similar.&lt;/p&gt;

&lt;p&gt;The consequence is not.&lt;/p&gt;

&lt;p&gt;That changes the evidence, controls and human authority we need.&lt;/p&gt;

&lt;p&gt;So I do not think an opportunity should be evaluated only on expected value.&lt;/p&gt;

&lt;p&gt;It should also be understood through its failure path.&lt;/p&gt;

&lt;p&gt;What happens if the system is wrong?&lt;/p&gt;

&lt;p&gt;Can the action be reversed?&lt;/p&gt;

&lt;p&gt;Will anyone know?&lt;/p&gt;

&lt;p&gt;Can the system fail safely?&lt;/p&gt;

&lt;p&gt;Can a human intervene?&lt;/p&gt;

&lt;p&gt;Can we identify which data, instruction, tool call, rule or human decision produced the behavior?&lt;/p&gt;

&lt;p&gt;If the answer to that last question is no, production may become difficult long before a demo reveals the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  If we cannot measure the change, we do not have a decision yet
&lt;/h2&gt;

&lt;p&gt;Another weakness in AI opportunity discussions is vague success.&lt;/p&gt;

&lt;p&gt;"Improve productivity."&lt;/p&gt;

&lt;p&gt;"Enhance customer experience."&lt;/p&gt;

&lt;p&gt;"Use AI to make employees more efficient."&lt;/p&gt;

&lt;p&gt;These may describe the intent. They do not give us a decision threshold.&lt;/p&gt;

&lt;p&gt;Before committing to an AI initiative, I want a baseline.&lt;/p&gt;

&lt;p&gt;How long does this take today?&lt;/p&gt;

&lt;p&gt;What does it cost?&lt;/p&gt;

&lt;p&gt;How often does it fail?&lt;/p&gt;

&lt;p&gt;How much work waits?&lt;/p&gt;

&lt;p&gt;How much skilled capacity is being consumed?&lt;/p&gt;

&lt;p&gt;How much variation exists between operators?&lt;/p&gt;

&lt;p&gt;What demand goes unanswered?&lt;/p&gt;

&lt;p&gt;Then we can define what meaningful improvement would look like.&lt;/p&gt;

&lt;p&gt;Maybe the proposed system reduces response time from hours to minutes.&lt;/p&gt;

&lt;p&gt;Maybe it gives ten employees access to knowledge that previously depended on two specialists.&lt;/p&gt;

&lt;p&gt;Maybe it shortens a research process without reducing decision quality.&lt;/p&gt;

&lt;p&gt;Maybe the expected productivity benefit disappears after we include review time.&lt;/p&gt;

&lt;p&gt;All of those are useful outcomes because they tell us something.&lt;/p&gt;

&lt;p&gt;A negative finding can be valuable too.&lt;/p&gt;

&lt;p&gt;It may save an organization from scaling an idea that looked impressive in a demo but did not change the economics of the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  A score comes later
&lt;/h2&gt;

&lt;p&gt;Use-case prioritization matrices are useful.&lt;/p&gt;

&lt;p&gt;I use structured evaluation too.&lt;/p&gt;

&lt;p&gt;But I do not think a weighted score should be allowed to rescue an idea that fails a fundamental decision gate.&lt;/p&gt;

&lt;p&gt;A project might have high expected value, strong executive enthusiasm and easy technical feasibility.&lt;/p&gt;

&lt;p&gt;That still does not make it a good AI project if ordinary automation solves the problem better.&lt;/p&gt;

&lt;p&gt;Or if the system needs access the organization cannot responsibly provide.&lt;/p&gt;

&lt;p&gt;Or if the outcome cannot be measured.&lt;/p&gt;

&lt;p&gt;Or if nobody will own the system once the project team leaves.&lt;/p&gt;

&lt;p&gt;A score is most useful after qualification, when several legitimate opportunities need to be compared.&lt;/p&gt;

&lt;p&gt;Before that, the harder questions matter more.&lt;/p&gt;

&lt;p&gt;Current enterprise guidance already does useful work around use-case discovery, repetitive work, skill bottlenecks, ambiguity, impact-versus-effort prioritization and workflow mapping. I see this framework as a complement to that work, not a replacement for it. The additional question I want to force earlier is whether the opportunity deserves to become an AI system at all.&lt;/p&gt;

&lt;p&gt;This is also where I depart from a common impact-versus-effort approach. Impact and effort are useful portfolio dimensions, but they answer a later question: which qualified opportunities deserve attention first? They do not tell us whether a candidate should have entered the portfolio in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Opportunity Decision Map
&lt;/h2&gt;

&lt;p&gt;I move an opportunity through six decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Find the pressure
&lt;/h3&gt;

&lt;p&gt;Identify what is waiting, constrained, fragmented, inconsistent, expensive, underused or currently impossible.&lt;/p&gt;

&lt;p&gt;Do not name the technology yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Define the change
&lt;/h3&gt;

&lt;p&gt;Describe the operating outcome before describing the solution.&lt;/p&gt;

&lt;p&gt;Establish a baseline and an outcome owner.&lt;/p&gt;

&lt;p&gt;If we cannot explain what should be different, the idea is not ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Test the simpler intervention
&lt;/h3&gt;

&lt;p&gt;Ask whether process change, existing software, deterministic automation, integration or removal of unnecessary work solves enough of the problem.&lt;/p&gt;

&lt;p&gt;AI has to beat the simpler intervention, not merely the current pain.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Qualify the AI fit and authority
&lt;/h3&gt;

&lt;p&gt;Determine what AI contributes that matters.&lt;/p&gt;

&lt;p&gt;Then define what information it needs, what it may do, what remains human and what failure would mean.&lt;/p&gt;

&lt;p&gt;This is where a feature idea begins to become a system decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Decide what must be proved
&lt;/h3&gt;

&lt;p&gt;List the assumptions that would change the decision if they turn out to be wrong.&lt;/p&gt;

&lt;p&gt;Define the baseline, success threshold, failure criteria and evidence needed before a larger commitment.&lt;/p&gt;

&lt;p&gt;The test is not "can we make a demo work?"&lt;/p&gt;

&lt;p&gt;The test is "what evidence would justify the next decision?"&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Test whether the system can be operated
&lt;/h3&gt;

&lt;p&gt;Ask who owns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;quality&lt;/li&gt;
&lt;li&gt;access&lt;/li&gt;
&lt;li&gt;exceptions&lt;/li&gt;
&lt;li&gt;cost&lt;/li&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;li&gt;vendor and model changes&lt;/li&gt;
&lt;li&gt;human escalation&lt;/li&gt;
&lt;li&gt;failure recovery&lt;/li&gt;
&lt;li&gt;improvement after deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A project that has no operating owner is not production-ready, even if the prototype is excellent.&lt;/p&gt;

&lt;p&gt;That sequence mirrors how I think about AI more broadly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Research, Reframe, Prove and Decide, Architect, Mobilize, Improve.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Research the situation.&lt;/p&gt;

&lt;p&gt;Reframe the problem before choosing the technology.&lt;/p&gt;

&lt;p&gt;Prove what matters and make the decision.&lt;/p&gt;

&lt;p&gt;Architect the system, including authority and failure paths.&lt;/p&gt;

&lt;p&gt;Mobilize the people, ownership and controls around it.&lt;/p&gt;

&lt;p&gt;Improve it from production evidence.&lt;/p&gt;

&lt;p&gt;The idea is not to slow AI down.&lt;/p&gt;

&lt;p&gt;It is to avoid spending speed on the wrong decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  One example from building a voice AI system
&lt;/h2&gt;

&lt;p&gt;When I worked on &lt;a href="https://subodhkc.com/solutions/kestrelvoice" rel="noopener noreferrer"&gt;Kestrel Voice&lt;/a&gt;, "build an AI voice agent" was never a useful enough problem statement.&lt;/p&gt;

&lt;p&gt;The technology was obvious.&lt;/p&gt;

&lt;p&gt;The business problem was more interesting.&lt;/p&gt;

&lt;p&gt;What should happen when a business cannot answer every customer interaction?&lt;/p&gt;

&lt;p&gt;A voice model that can carry a conversation solves only part of that.&lt;/p&gt;

&lt;p&gt;The useful outcome may require the system to understand why someone called, answer from approved information, collect what the business needs, book something, transfer the conversation when appropriate, preserve the result, and make sure somebody knows what happened.&lt;/p&gt;

&lt;p&gt;Once you look at the whole operating outcome, the architecture changes.&lt;/p&gt;

&lt;p&gt;Correct escalation can matter more than sounding perfectly human.&lt;/p&gt;

&lt;p&gt;Business rules have to exist before they can be automated.&lt;/p&gt;

&lt;p&gt;Cost, monitoring and failure recovery become product features instead of afterthoughts.&lt;/p&gt;

&lt;p&gt;That experience reinforced something I now use as a general test:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The use case is not the AI capability. The use case is the operating change the capability has to produce.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"AI voice agent" names a technology.&lt;/p&gt;

&lt;p&gt;"Make sure valuable customer demand does not disappear when the team cannot answer" describes something worth investigating.&lt;/p&gt;

&lt;p&gt;One produces a demo.&lt;/p&gt;

&lt;p&gt;The other can produce an operating decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same opportunity can end in different decisions
&lt;/h2&gt;

&lt;p&gt;A good investigation does not have to result in "deploy AI."&lt;/p&gt;

&lt;p&gt;I would expect opportunities to leave this process in several different states.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explore
&lt;/h3&gt;

&lt;p&gt;The pressure appears important, but important assumptions remain.&lt;/p&gt;

&lt;p&gt;We need more evidence before deciding whether an AI intervention is worth testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prove
&lt;/h3&gt;

&lt;p&gt;The opportunity is strong enough to justify a bounded evidence test.&lt;/p&gt;

&lt;p&gt;The next step should be designed around the assumptions that could kill the idea.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pursue
&lt;/h3&gt;

&lt;p&gt;The opportunity has enough evidence and operating clarity to move into solution design, vendor selection or architecture.&lt;/p&gt;

&lt;p&gt;This does not mean "build immediately." It means the problem has earned the next commitment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not AI
&lt;/h3&gt;

&lt;p&gt;The problem matters, but another intervention is better.&lt;/p&gt;

&lt;p&gt;That can be a successful result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wait
&lt;/h3&gt;

&lt;p&gt;AI may fit later, but capability, economics, data, authority, ownership or another dependency is not ready.&lt;/p&gt;

&lt;p&gt;A Wait decision should include a revisit trigger.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stop
&lt;/h3&gt;

&lt;p&gt;The opportunity does not justify further investment.&lt;/p&gt;

&lt;p&gt;A company that can stop weak AI ideas is in a better position to fund the strong ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-page AI Opportunity Card
&lt;/h2&gt;

&lt;p&gt;For each candidate, I would preserve the decision in a record like this:&lt;br&gt;
FieldDecision purposeOperating pressureWhat is happening that deserves attention?Affected people or processWho experiences it?BaselineWhat happens today?Desired changeWhat should be different?Business consequenceWhy does the change matter?Outcome ownerWho is accountable for the result?Simpler interventionCan process, software or rules solve it first?AI-specific advantageWhat does AI contribute that matters?Required context or dataWhat must the system know or access?Authority boundaryWhat may AI recommend, communicate, decide, change or trigger?Human authorityWhat remains explicitly human?Failure pathWhat happens when it is wrong or unavailable?TraceabilityCan behavior be traced to inputs, instructions, tools and owners?Success evidenceWhat baseline and threshold would prove value?Operating ownerWho owns quality, cost, exceptions and change in production?Key assumptionsWhat remains unproved?DecisionExplore, Prove, Pursue, Not AI, Wait, or StopRevisit triggerWhat new evidence would change the decision?&lt;br&gt;
That last field matters.&lt;/p&gt;

&lt;p&gt;AI capability changes. Vendor economics change. Regulations change. Internal data improves. Integration becomes easier. A "Wait" decision today may become a strong opportunity later.&lt;/p&gt;

&lt;p&gt;A decision framework should preserve that context instead of forcing every idea into approved or rejected.&lt;/p&gt;

&lt;h2&gt;
  
  
  After an opportunity survives
&lt;/h2&gt;

&lt;p&gt;Finding the opportunity is only the first decision.&lt;/p&gt;

&lt;p&gt;If it survives, a different set of questions begins.&lt;/p&gt;

&lt;p&gt;Should we buy a product, configure something we already have, integrate a vendor capability, build a differentiated layer ourselves, or wait?&lt;/p&gt;

&lt;p&gt;If we need a proof of concept, what exactly must it prove?&lt;/p&gt;

&lt;p&gt;If a vendor is involved, what dependency are we accepting along with the capability?&lt;/p&gt;

&lt;p&gt;What architecture preserves the right authority boundaries?&lt;/p&gt;

&lt;p&gt;What must be observable once the system is live?&lt;/p&gt;

&lt;p&gt;Those deserve separate treatment because each is a substantial decision on its own.&lt;/p&gt;

&lt;p&gt;The important part is that we do not jump to them too early.&lt;/p&gt;

&lt;p&gt;A company will always be able to find more places where AI could be used than it has the capacity to pursue.&lt;/p&gt;

&lt;p&gt;The harder question is which part of the business is worth changing, whether AI materially improves the intervention, and what evidence would justify the next commitment.&lt;/p&gt;

&lt;p&gt;If those answers are still unclear, the next step is not implementation.&lt;/p&gt;

&lt;p&gt;It is investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Companion Artifact: AI Opportunity Decision Map
&lt;/h2&gt;

&lt;p&gt;The complete decision flow in one view:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FIND THE PRESSURE&lt;/strong&gt;
Waiting, capacity, information, handoffs, variation, expertise bottleneck, unserved demand, previously uneconomic capability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DEFINE THE CHANGE&lt;/strong&gt;
Current baseline to desired operating outcome to accountable owner&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TEST THE SIMPLER INTERVENTION&lt;/strong&gt;
Process change? Existing software? Deterministic automation? Integration? Remove the work?
If a simpler intervention solves enough of the problem, the answer is &lt;strong&gt;NOT AI&lt;/strong&gt;. If not, continue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QUALIFY AI FIT AND AUTHORITY&lt;/strong&gt;
What does AI uniquely contribute? What can it see? What can it say? What can it change? What remains human? What happens when it is wrong? Can the behavior be traced?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DEFINE THE EVIDENCE GATE&lt;/strong&gt;
Baseline, hypothesis, success threshold, failure threshold, key assumptions.
Insufficient evidence means &lt;strong&gt;EXPLORE&lt;/strong&gt;. Enough to test means &lt;strong&gt;PROVE&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OPERATING READINESS&lt;/strong&gt;
Owner, monitoring, cost, access, exceptions, escalation, change, fallback, improvement.
Ready means &lt;strong&gt;PURSUE&lt;/strong&gt;. Not ready but potentially viable means &lt;strong&gt;WAIT&lt;/strong&gt;. Not justified means &lt;strong&gt;STOP&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Source and methodology notes
&lt;/h2&gt;

&lt;p&gt;This article is intentionally not a list of AI use cases. Current enterprise guidance already covers common AI opportunity discovery patterns such as repetitive work, skill bottlenecks, ambiguity, use-case gathering, impact-versus-effort prioritization and workflow mapping. The framework above adds a different layer: operating pressure first, simpler alternatives before AI, explicit authority and failure boundaries, evidence gates, traceability, and production ownership.&lt;/p&gt;

&lt;p&gt;For context on existing enterprise use-case guidance, OpenAI's &lt;a href="https://openai.com/business/guides-and-resources/identifying-and-scaling-ai-use-cases/" rel="noopener noreferrer"&gt;Identifying and scaling AI use cases&lt;/a&gt; covers repetitive low-value work, skill bottlenecks, ambiguity, use-case primitives, impact-versus-effort prioritization and department workflow mapping. OpenAI's &lt;a href="https://openai.com/business/guides-and-resources/how-enterprises-are-scaling-ai/" rel="noopener noreferrer"&gt;How enterprises are scaling AI&lt;/a&gt; emphasizes workflow design, ownership, quality before scale, governance as an enabler and protecting human judgment.&lt;/p&gt;

&lt;p&gt;Internal methodology references: &lt;a href="https://subodhkc.com/cognitive-systems-management" rel="noopener noreferrer"&gt;Cognitive Systems Management (CSM)&lt;/a&gt; supplies compatible operating principles around ownership, risk, monitoring and continuous operation. The &lt;a href="https://subodhkc.com/packages/isaf" rel="noopener noreferrer"&gt;Instruction Stack Audit Framework (ISAF)&lt;/a&gt; supplies compatible principles around instruction traceability, layer ownership and accountability. The AI Opportunity Decision Map is a separate executive opportunity framework authored for this article. It is not part of ISAF.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How is this framework different from an AI use case prioritization matrix?
&lt;/h3&gt;

&lt;p&gt;A prioritization matrix is useful after qualification, when several legitimate opportunities need to be compared. This framework handles the earlier question: whether a candidate should enter the portfolio at all. It applies decision gates before scoring.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should every AI opportunity go through all six decisions?
&lt;/h3&gt;

&lt;p&gt;Yes for any opportunity that would require production resources. For very small experiments, a compressed version still helps: identify the pressure, test the simpler intervention, and define what would count as evidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the most common mistake in AI use case discovery?
&lt;/h3&gt;

&lt;p&gt;Starting with the technology instead of the operating pressure. Teams search the company for places to install AI rather than looking for what should change. That produces demos, not decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  When should a company decide Not AI for an opportunity?
&lt;/h3&gt;

&lt;p&gt;When a simpler intervention solves enough of the problem. Process change, existing software, deterministic automation, or removing an unnecessary step may be the better answer. Not AI is a successful result when it saves the organization from unnecessary complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does this connect to ongoing AI advisory?
&lt;/h3&gt;

&lt;p&gt;The framework identifies qualified opportunities. For recurring decisions across multiple opportunities, ongoing advisory through &lt;a href="https://subodhkc.com/ai-advisor" rel="noopener noreferrer"&gt;AI Advisor for Business&lt;/a&gt; provides continuous judgment support. For assessing one specific workflow, the &lt;a href="https://subodhkc.com/ai-automation" rel="noopener noreferrer"&gt;AI Opportunity and Workflow Assessment&lt;/a&gt; applies this methodology to a real opportunity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trying to determine whether a workflow is actually worth pursuing with AI?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://subodhkc.com/ai-automation" rel="noopener noreferrer"&gt;AI Opportunity and Workflow Assessment&lt;/a&gt; examines one opportunity, the current workflow, the alternatives, the evidence and the path forward before you commit to implementation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://subodhkc.com/blog/where-should-company-use-ai" rel="noopener noreferrer"&gt;subodhkc.com&lt;/a&gt;. Explore &lt;a href="https://kestrelvoice.com" rel="noopener noreferrer"&gt;KestrelVoice AI Voice Operations&lt;/a&gt;. Follow for more on AI governance, enterprise architecture, and compliance engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiusecasediscovery</category>
      <category>aiusecasediscoveryfr</category>
      <category>aiusecaseprioritizat</category>
      <category>howtoidentifyaiuseca</category>
    </item>
    <item>
      <title>Is Your AI Pilot Production-Ready? 12 Checks Before You Ship</title>
      <dc:creator>Subodh Kc</dc:creator>
      <pubDate>Tue, 13 Oct 2026 15:18:53 +0000</pubDate>
      <link>https://dev.to/subodhkc/is-your-ai-pilot-production-ready-12-checks-before-you-ship-38o0</link>
      <guid>https://dev.to/subodhkc/is-your-ai-pilot-production-ready-12-checks-before-you-ship-38o0</guid>
      <description>&lt;h2&gt;
  
  
  The Gate That Separates Pilot From Production
&lt;/h2&gt;

&lt;p&gt;Most AI pilots die in the gap between demo and production. The demo works on clean data in a controlled environment. Production introduces adversarial inputs, multi-tenant data isolation requirements, regulatory evidence obligations, and latency budgets that the demo never faced. In my former role as an AI Strategy Core Team Lead at a Fortune 100 company, I managed 53 enterprise applications through this gap. The pilots that survived shared one trait: they passed a structured readiness gate before touching production traffic.&lt;/p&gt;

&lt;p&gt;This is not a generic checklist. Each check below maps to a specific failure mode I have seen in production AI systems, the architectural control that prevents it, and the evidence artifact required for audit. If your pilot cannot produce these artifacts, it is not ready for production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 1: Data Provenance and Lineage Verification
&lt;/h2&gt;

&lt;p&gt;Every input feature must trace back to a verified source with a documented transformation pipeline. In production, RAG poisoning occurs when untrusted content enters the knowledge base without lineage controls. The readiness check requires a data lineage map showing every source, transformation step, and access control point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure mode:&lt;/strong&gt; Untrusted data enters the pipeline through a vendor API or user-generated content, poisoning model outputs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural control:&lt;/strong&gt; Implement a data provenance layer that tags every input with source, timestamp, and trust score. Use row-level security to isolate tenant-specific data. For RAG systems, enforce a content quarantine where new documents are validated against schema and content policies before entering the vector database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence artifact:&lt;/strong&gt; Data lineage diagram showing source-to-feature mapping, with access control points marked. See &lt;a href="https://subodhkc.com/secure-enterprise-rag-architecture" rel="noopener noreferrer"&gt;secure enterprise RAG architecture&lt;/a&gt; for the reference pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 2: Model Robustness Under Adversarial Load
&lt;/h2&gt;

&lt;p&gt;Stress testing in staging does not predict production behavior. Production traffic includes prompt injection attempts, malformed inputs, and edge cases that staging never sees. The readiness check requires adversarial testing results showing model behavior under attack scenarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure mode:&lt;/strong&gt; Model produces harmful outputs when subjected to prompt injection, jailbreak attempts, or distribution shift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural control:&lt;/strong&gt; Run the model through an adversarial test suite before deployment. Include prompt injection patterns from the &lt;a href="https://owasp.org/www-project-top-10-for-large-language-model-applications/" rel="noopener noreferrer"&gt;OWASP Top 10 for LLMs&lt;/a&gt;, data exfiltration attempts, and model extraction attacks. Measure the rejection rate for each attack class. Set a threshold: if the model fails more than 2% of adversarial tests, it does not pass this gate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence artifact:&lt;/strong&gt; Adversarial test report with pass/fail rates per attack class, signed by the security team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 3: Multi-Tenant Data Isolation
&lt;/h2&gt;

&lt;p&gt;If your AI system serves multiple tenants, data isolation is not optional. Cross-tenant data leakage is a regulatory violation under GDPR, HIPAA, and most enterprise contracts. The readiness check requires proof that Tenant A cannot access Tenant B's data through any query path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure mode:&lt;/strong&gt; RAG retrieval returns documents from the wrong tenant. Vector search bypasses row-level security because the embedding model operates on shared indexes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural control:&lt;/strong&gt; Implement tenant-scoped vector indexes or enforce row-level security at the retrieval layer. Test isolation by attempting cross-tenant queries with Tenant A credentials against Tenant B data. The test must fail 100% of the time. For shared infrastructure, use namespace isolation in the vector database and verify that the retrieval filter chain enforces tenant boundaries. See &lt;a href="https://subodhkc.com/blog/implementing-rag-row-level-security-for-multi-tenant-ai" rel="noopener noreferrer"&gt;implementing RAG row-level security for multi-tenant AI&lt;/a&gt; for the implementation pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence artifact:&lt;/strong&gt; Isolation test results showing cross-tenant query rejection rate (must be 100%).&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 4: Runtime Monitoring and Drift Detection
&lt;/h2&gt;

&lt;p&gt;A model that passes pre-deployment testing can drift in production. Input distributions shift, model behavior changes, and degradation is often invisible until a user complains. The readiness check requires a deployed monitoring stack with defined alert thresholds before the pilot goes live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure mode:&lt;/strong&gt; Model quality degrades silently over weeks. The team discovers the issue when a customer reports incorrect results, by which point the damage is done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural control:&lt;/strong&gt; Deploy three monitoring layers before production traffic: (1) input distribution monitoring to detect data drift, (2) output quality monitoring to detect response degradation, and (3) latency monitoring to detect infrastructure issues. Set alert thresholds based on baseline measurements from staging. Use &lt;a href="https://subodhkc.com/products/llmverify" rel="noopener noreferrer"&gt;runtime verification tools&lt;/a&gt; to capture deterministic checks on probabilistic outputs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence artifact:&lt;/strong&gt; Monitoring dashboard URL, alert configuration with thresholds, and escalation runbook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 5: Latency Budget Compliance
&lt;/h2&gt;

&lt;p&gt;Every AI system has a latency budget. For voice agents, the budget is 800ms round-trip before callers notice degradation. For RAG systems, the budget is typically 2-3 seconds before users abandon the query. The readiness check requires measured latency at p50, p95, and p99 under production-equivalent load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure mode:&lt;/strong&gt; System works in staging with 10 concurrent users. In production with 500 concurrent users, latency spikes to 12 seconds and users abandon the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural control:&lt;/strong&gt; Run load testing at 2x expected peak traffic. Measure p50, p95, and p99 latency for the full request path: input processing, model inference, retrieval, response generation. If p99 latency exceeds the budget, optimize before deployment. Common fixes: implement response caching, use a model router to send simple queries to faster models, or pre-compute embeddings for high-frequency queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence artifact:&lt;/strong&gt; Load test report showing p50/p95/p99 latency at 2x peak load, with pass/fail against the latency budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 6: Fallback and Escalation Architecture
&lt;/h2&gt;

&lt;p&gt;Production AI systems fail. The question is not whether, but when, and what happens when they do. The readiness check requires a documented fallback chain: what happens when the model is unavailable, when latency exceeds thresholds, and when outputs fail quality checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure mode:&lt;/strong&gt; Model API goes down. The AI system returns errors to users. No fallback exists. The pilot is labeled a failure and the project dies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural control:&lt;/strong&gt; Implement a three-tier fallback chain: (1) cached response for common queries, (2) deterministic rule-based response for queries the model cannot handle, (3) human escalation for high-stakes decisions. For voice agents, this means IVR fallback, SMS escalation, and human handoff. See &lt;a href="https://subodhkc.com/why-ai-voice-agents-fail-in-production" rel="noopener noreferrer"&gt;why AI voice agents fail in production&lt;/a&gt; for the reference architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence artifact:&lt;/strong&gt; Fallback flowchart showing the decision tree for model unavailability, latency timeout, and quality failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 7: Compliance Evidence Pipeline
&lt;/h2&gt;

&lt;p&gt;Under TRAIGA, the EU AI Act, and NYC Local Law 144, you must produce evidence that your AI system was tested, monitored, and governed. A compliance dashboard that shows current status is not evidence. Evidence is an immutable, timestamped record of what happened, when, and who approved it. The readiness check requires a deployed evidence pipeline before production traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure mode:&lt;/strong&gt; Regulator requests evidence of bias testing. The team has a dashboard showing current bias metrics, but no historical record of when tests were run, what the results were, or who signed off. The organization faces a &lt;a href="https://eur-lex.europa.eu/" rel="noopener noreferrer"&gt;regulatory penalty&lt;/a&gt; for insufficient documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural control:&lt;/strong&gt; Implement an immutable audit trail that captures every model decision, input, output, and human override. Use append-only storage (not a mutable database). Capture: timestamp, user ID, model version, input hash, output, confidence score, and any human review action. See &lt;a href="https://subodhkc.com/how-to-secure-and-govern-ai" rel="noopener noreferrer"&gt;AI governance and evidence architecture&lt;/a&gt; for the reference implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence artifact:&lt;/strong&gt; Evidence pipeline architecture diagram, sample audit trail export, and retention policy documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 8: Security Threat Model and Penetration Test
&lt;/h2&gt;

&lt;p&gt;AI systems have unique attack surfaces that traditional security testing misses. Prompt injection, model extraction, data exfiltration through crafted queries, and adversarial inputs that bypass content filters are all AI-specific threats. The readiness check requires a threat model that covers these attack vectors and a penetration test that validates the defenses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure mode:&lt;/strong&gt; Attacker uses prompt injection to extract system prompts, customer data, or model weights. The team did not test for this because traditional pen testing does not cover AI-specific attack vectors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural control:&lt;/strong&gt; Build a threat model using the &lt;a href="https://nist.gov/itl/ai-risk-management-framework" rel="noopener noreferrer"&gt;NIST AI RMF&lt;/a&gt; framework. Include: prompt injection, data exfiltration, model extraction, denial of service through expensive queries, and supply chain attacks via model weights or plugins. Run a penetration test that attempts each attack class. Document the defenses and their effectiveness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence artifact:&lt;/strong&gt; Threat model document, penetration test report with attack-by-attack results, and remediation plan for any failed tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 9: Human-in-the-Loop Decision Points
&lt;/h2&gt;

&lt;p&gt;Not every AI decision should be automated. High-stakes decisions (financial, legal, healthcare) require human review. The readiness check requires a documented decision framework that specifies which outputs are automated, which require human review, and which require human approval before action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure mode:&lt;/strong&gt; AI system automates a decision that should have had human review. The decision causes harm. The organization is liable because there was no human checkpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural control:&lt;/strong&gt; Define a decision matrix mapping output risk levels to review requirements: (1) low risk: automated, (2) medium risk: human review after action, (3) high risk: human approval before action. Implement the matrix as a policy layer in the application, not as a documentation note. The policy layer must be testable and auditable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence artifact:&lt;/strong&gt; Decision matrix document, policy implementation code, and test results showing that high-risk outputs are blocked until human approval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 10: Cost and Resource Budget
&lt;/h2&gt;

&lt;p&gt;AI systems have variable costs that can spike unpredictably. A prompt that costs $0.01 in staging can cost $5.00 in production if a user crafts a query that triggers a long chain of tool calls. The readiness check requires a cost model showing projected monthly spend at expected traffic, and a circuit breaker that cuts off spending before it exceeds the budget.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure mode:&lt;/strong&gt; Pilot goes live. A user discovers a query pattern that triggers expensive model calls. The team gets a $50,000 API bill in the first week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural control:&lt;/strong&gt; Implement a cost monitoring layer that tracks spend per user, per query, and per model. Set a circuit breaker that rate-limits or degrades service when spend exceeds 80% of the daily budget. Build a cost model that projects monthly spend at expected traffic, and validate it against the first 48 hours of production traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence artifact:&lt;/strong&gt; Cost model spreadsheet, circuit breaker configuration, and 48-hour cost validation report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 11: Rollback and Version Management
&lt;/h2&gt;

&lt;p&gt;When a production AI system degrades, you need to roll back to the previous version quickly. This requires versioned models, versioned prompts, versioned retrieval indexes, and a rollback procedure that takes minutes, not hours. The readiness check requires a tested rollback procedure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure mode:&lt;/strong&gt; New model version degrades output quality. The team tries to roll back but discovers the previous model version was overwritten, the prompt template was changed without version control, and the vector index was rebuilt with different embeddings. Rollback takes 6 hours instead of 6 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural control:&lt;/strong&gt; Version every component: model weights, prompt templates, retrieval indexes, and configuration. Store previous versions with retention policies. Test the rollback procedure by deploying a new version, then rolling back, and measuring the time. The rollback must complete in under 15 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence artifact:&lt;/strong&gt; Version management policy, rollback test results with measured time, and rollback runbook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check 12: Documentation and Operational Runbook
&lt;/h2&gt;

&lt;p&gt;The team that builds the pilot will not be the team that operates it in production. The readiness check requires operational documentation that a new on-call engineer can follow to diagnose and resolve issues without contacting the original builders.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure mode:&lt;/strong&gt; Pilot goes live. The original builders move to the next project. Production issue occurs at 2 AM. The on-call engineer has no documentation, no runbook, and no idea how the system works. Mean time to resolution: 4 hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architectural control:&lt;/strong&gt; Produce three documents before production: (1) system architecture diagram showing all components and data flows, (2) operational runbook with step-by-step procedures for common incidents, (3) on-call guide with escalation contacts and decision trees. Have a new engineer read the documentation and attempt to diagnose a simulated incident. If they cannot, the documentation is insufficient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evidence artifact:&lt;/strong&gt; Architecture diagram, operational runbook, on-call guide, and the results of the new-engineer documentation test.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Gate Decision
&lt;/h2&gt;

&lt;p&gt;These 12 checks are not suggestions. They are the gate between pilot and production. If any check fails, the pilot does not go live. If the team cannot produce the evidence artifact for any check, the pilot does not go live.&lt;/p&gt;

&lt;p&gt;In my Fortune 100 experience, this gate prevented at least 4 deployments that would have caused production incidents. In each case, the team believed the system was ready because the demo worked. The gate revealed what the demo could not: adversarial vulnerabilities, missing isolation controls, absent evidence pipelines, and undocumented rollback procedures.&lt;/p&gt;

&lt;p&gt;If you are deploying high-stakes enterprise AI and need an independent systems advisor to audit your readiness before regulators or production traffic do, &lt;a href="https://subodhkc.com/advisory" rel="noopener noreferrer"&gt;schedule a strategic evaluation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the most common reason AI pilots fail the readiness gate?&lt;/strong&gt; Missing evidence pipelines. Teams build the system but do not build the audit trail. When regulators or internal auditors request evidence, there is nothing to show.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long should the readiness check process take?&lt;/strong&gt; For a well-architected system, 1-2 weeks. For a system that has not considered these checks, 4-8 weeks. The checks are not the bottleneck. The architectural work to satisfy them is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can these checks be automated?&lt;/strong&gt; Checks 1, 3, 5, 8, and 11 can be partially automated with test suites and monitoring tools. Checks 2, 4, 6, 7, 9, 10, and 12 require human judgment and architectural review. Do not attempt to fully automate the gate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens if a check fails?&lt;/strong&gt; The pilot does not go to production. The team addresses the failure, re-runs the check, and re-presents at the gate. There is no partial pass.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://subodhkc.com/blog/12-production-readiness-checks-for-ai-pilots" rel="noopener noreferrer"&gt;subodhkc.com&lt;/a&gt;. Explore &lt;a href="https://kestrelvoice.com" rel="noopener noreferrer"&gt;KestrelVoice AI Voice Operations&lt;/a&gt;. Follow for more on AI governance, enterprise architecture, and compliance engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiproductionreadines</category>
      <category>aipilotreadinesschec</category>
      <category>enterpriseaideployme</category>
      <category>aicomplianceevidence</category>
    </item>
    <item>
      <title>Is AI-Generated Frontend Code Hurting Your SEO?</title>
      <dc:creator>Subodh Kc</dc:creator>
      <pubDate>Thu, 08 Oct 2026 15:17:22 +0000</pubDate>
      <link>https://dev.to/subodhkc/is-ai-generated-frontend-code-hurting-your-seo-5eh5</link>
      <guid>https://dev.to/subodhkc/is-ai-generated-frontend-code-hurting-your-seo-5eh5</guid>
      <description>&lt;p&gt;An AI assistant can now produce a complete, working Next.js page in minutes: components, API calls, navigation, interactions. It looks done. It renders in a browser. And parts of it are invisible to Google.&lt;/p&gt;

&lt;p&gt;Code that works correctly for a human visitor is not necessarily structured correctly for a search crawler. That gap is what this whitepaper examines.&lt;/p&gt;

&lt;p&gt;This whitepaper examines an architectural risk observed during the development of an AI-assisted frontend: important public content was being fetched or constructed in the browser rather than included in the initial HTML response. Despite conventional SEO work, performance optimization and manual indexing requests, some pages took more than two weeks to appear in Google's index.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key finding:&lt;/strong&gt; When titles, body content, links or structured information depend on browser-side JavaScript, crawlers must perform additional rendering work before they can fully understand the page. This creates an avoidable crawlability dependency that can delay or prevent indexing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Published: July 29, 2026 · Author: Subodh KC · Publication type: Technical position paper&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;The observation does not prove that client-side rendering was the sole cause of the delay. Google itself states that crawling may take anywhere from several days to several weeks and that requesting indexing does not guarantee immediate inclusion.&lt;/p&gt;

&lt;p&gt;It does, however, expose a meaningful engineering risk. When titles, body content, links or structured information depend on browser-side JavaScript, crawlers must perform additional rendering work before they can fully understand the page. Google supports JavaScript rendering, but rendering is a separate processing stage and may not occur immediately. Other crawlers may have different or undocumented JavaScript capabilities.&lt;/p&gt;

&lt;p&gt;The appropriate conclusion is not that JavaScript or Client Components are inherently harmful. The conclusion is that public, search-critical information should be available in the server-delivered HTML whenever practical.&lt;/p&gt;

&lt;p&gt;For a complementary perspective on making websites accessible to AI agents and crawlers, see our related article on &lt;a href="https://subodhkc.com/blog/what-is-llms-txt" rel="noopener noreferrer"&gt;llms.txt and AI-ready website architecture&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Initial Observation
&lt;/h2&gt;

&lt;p&gt;The website examined in this case was developed using an AI coding copilot and a modern &lt;a href="https://subodhkc.com/services" rel="noopener noreferrer"&gt;Next.js frontend architecture&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Standard SEO controls had been implemented, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page titles and descriptions&lt;/li&gt;
&lt;li&gt;Sitemap generation&lt;/li&gt;
&lt;li&gt;Robots directives&lt;/li&gt;
&lt;li&gt;Canonical URLs&lt;/li&gt;
&lt;li&gt;Page-performance improvements&lt;/li&gt;
&lt;li&gt;Internal links&lt;/li&gt;
&lt;li&gt;Manual indexing requests through Google Search Console&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nevertheless, newly published pages were not consistently indexed within the expected timeframe. Some manually submitted pages took more than two weeks to be crawled or indexed.&lt;/p&gt;

&lt;p&gt;A technical inspection found that portions of the public content were dependent on client-side execution. In several cases, the server returned a partial interface or loading state, while the meaningful content appeared only after JavaScript executed and completed an API request.&lt;/p&gt;

&lt;p&gt;This established a plausible technical mechanism, but not standalone proof of causation. Google identifies several other reasons that pages may not be indexed, including content quality, duplicate or canonicalized pages, crawl discovery, server capacity, robots directives and general crawl prioritization.&lt;/p&gt;

&lt;p&gt;The finding should therefore be treated as an architectural risk requiring controlled validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What Client-Side Rendering Actually Means
&lt;/h2&gt;

&lt;p&gt;Client-side rendering occurs when the browser receives a limited HTML document and relies on JavaScript to fetch data or construct the meaningful page content.&lt;/p&gt;

&lt;p&gt;A simplified client-rendered sequence is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server returns an HTML shell.&lt;/li&gt;
&lt;li&gt;The browser downloads JavaScript.&lt;/li&gt;
&lt;li&gt;React initializes.&lt;/li&gt;
&lt;li&gt;A browser-side API request begins.&lt;/li&gt;
&lt;li&gt;The API returns the page data.&lt;/li&gt;
&lt;li&gt;React inserts the content into the document.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By contrast, server-rendered or statically generated pages include their primary content in the initial HTML response.&lt;/p&gt;

&lt;p&gt;Google processes JavaScript pages through crawling, rendering and indexing stages. It initially retrieves the page, extracts available links and then sends eligible pages to its rendering system. Pages whose content is already present in the response can be understood without depending on browser-side data loading.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important Next.js Distinction
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;"use client"&lt;/code&gt; directive does not automatically make a page invisible to crawlers.&lt;/p&gt;

&lt;p&gt;Next.js can pre-render Client Components into HTML for the initial page load. The actual risk appears when important content depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;useEffect&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Browser-only APIs&lt;/li&gt;
&lt;li&gt;SWR or TanStack Query requests without server-provided initial data&lt;/li&gt;
&lt;li&gt;User clicks&lt;/li&gt;
&lt;li&gt;Scroll events&lt;/li&gt;
&lt;li&gt;Local storage or session state&lt;/li&gt;
&lt;li&gt;Client-only dynamic imports&lt;/li&gt;
&lt;li&gt;Suspense fallbacks that replace primary content&lt;/li&gt;
&lt;li&gt;Metadata inserted after page load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next.js documentation confirms that Client Components may still be pre-rendered. Therefore, counting &lt;code&gt;"use client"&lt;/code&gt; directives is not a reliable SEO audit by itself.&lt;/p&gt;

&lt;p&gt;The correct audit question is:&lt;/p&gt;

&lt;p&gt;Is the page's important public content present in the HTML returned by the server?&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Why Client-Only Content Creates SEO Risk
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 Rendering Introduces an Additional Dependency
&lt;/h3&gt;

&lt;p&gt;Google can execute JavaScript using an evergreen Chromium renderer. However, rendering is a distinct processing stage, and a page may remain in the rendering queue until resources become available.&lt;/p&gt;

&lt;p&gt;Server rendering does not guarantee instant indexing. It removes one dependency from the indexing process.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Interaction-Dependent Content May Never Load
&lt;/h3&gt;

&lt;p&gt;Search crawlers generally do not click buttons, complete forms or scroll through a page as a human user would.&lt;/p&gt;

&lt;p&gt;Content loaded only after a click, swipe, typing event or scroll action may therefore remain undiscovered. Google specifically recommends that primary content should not depend on user interaction to load.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 Non-Crawlable Navigation Weakens Discovery
&lt;/h3&gt;

&lt;p&gt;A visual control is not automatically a crawlable link.&lt;/p&gt;

&lt;p&gt;Google generally discovers links through anchor elements containing an &lt;code&gt;href&lt;/code&gt; attribute. A button that calls &lt;code&gt;router.push()&lt;/code&gt; may work for users but does not provide the same explicit crawl path as an anchor or Next.js &lt;code&gt;&amp;amp;lt;Link&amp;amp;gt;&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
// Weak discovery pattern&lt;br&gt;
&amp;lt;button onClick={() =&amp;gt; router.push('/products/42')}&amp;gt;&lt;br&gt;
  View product&lt;br&gt;
&amp;lt;/button&amp;gt;&lt;br&gt;
A crawlable alternative is:&lt;br&gt;
import Link from 'next/link';&lt;/p&gt;

&lt;p&gt;&amp;lt;Link href="&lt;a href="https://subodhkc.com/products" rel="noopener noreferrer"&gt;https://subodhkc.com/products&lt;/a&gt;"&amp;gt;&lt;br&gt;
  View product&lt;br&gt;
&amp;lt;/Link&amp;gt;&lt;br&gt;
Programmatic routing remains appropriate for application actions. It should not replace semantic links used to navigate between public pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Client-Managed Metadata Can Arrive Too Late
&lt;/h3&gt;

&lt;p&gt;Titles, descriptions, canonical tags and Open Graph metadata should be included in the document head generated by the server.&lt;/p&gt;

&lt;p&gt;In Next.js App Router applications, metadata should normally be defined through the &lt;code&gt;metadata&lt;/code&gt; export, metadata files or &lt;code&gt;generateMetadata&lt;/code&gt;. Next.js then generates the corresponding head elements.&lt;/p&gt;

&lt;p&gt;Updating &lt;code&gt;document.title&lt;/code&gt; inside an effect may change what a user sees in the browser, but it creates unnecessary dependence on JavaScript and may produce incomplete previews for services that inspect the initial response.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.5 Excessive JavaScript Affects Users as Well as Crawlers
&lt;/h3&gt;

&lt;p&gt;Large JavaScript bundles, extensive hydration and unnecessary frontend libraries can degrade responsiveness and loading performance.&lt;/p&gt;

&lt;p&gt;Core Web Vitals are among the signals used by Google's ranking systems. However, poor metrics should not be described as producing an immediate or automatic ranking penalty. Google evaluates many signals, and relevance can outweigh page-experience deficiencies.&lt;/p&gt;

&lt;p&gt;The stronger argument is that excessive client-side work creates both usability and crawl-processing costs. This is especially relevant for &lt;a href="https://subodhkc.com/products" rel="noopener noreferrer"&gt;privacy-first AI tools&lt;/a&gt; that need to be discoverable.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Common AI-Assisted Frontend Failure Patterns
&lt;/h2&gt;

&lt;p&gt;AI coding tools are not inherently hostile to SEO. However, they optimize for completing the requested feature and may select technically functional patterns without considering crawler behavior unless SEO architecture is explicitly included in the instructions.&lt;/p&gt;

&lt;p&gt;Common risks include:&lt;/p&gt;

&lt;h3&gt;
  
  
  Client-Side API Fetching for Primary Content
&lt;/h3&gt;

&lt;p&gt;'use client';&lt;/p&gt;

&lt;p&gt;useEffect(() =&amp;gt; {&lt;br&gt;
  fetch('/api/article')&lt;br&gt;
    .then(response =&amp;gt; response.json())&lt;br&gt;
    .then(setArticle);&lt;br&gt;
}, []);&lt;br&gt;
If the initial HTML contains only a loading indicator, the article depends entirely on JavaScript rendering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interaction-Gated Content
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;{isOpen &amp;amp;amp;&amp;amp;amp; &amp;amp;lt;p&amp;amp;gt;{technicalDescription}&amp;amp;lt;/p&amp;amp;gt;}&lt;/code&gt;When the content is not present until a user clicks, crawlers may never receive it.&lt;/p&gt;

&lt;p&gt;A semantic alternative for basic expandable content is:&lt;br&gt;
&amp;lt;details&amp;gt;&lt;br&gt;
  &amp;lt;summary&amp;gt;Technical specifications&amp;lt;/summary&amp;gt;&lt;br&gt;
  &amp;lt;p&amp;gt;{technicalDescription}&amp;lt;/p&amp;gt;&lt;br&gt;
&amp;lt;/details&amp;gt;&lt;br&gt;
The text remains in the server-delivered HTML while the browser provides native interaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Infinite Scroll Without Crawlable Pagination
&lt;/h3&gt;

&lt;p&gt;Infinite scrolling may improve user experience, but every meaningful result should also have a stable URL and a crawlable paginated path.&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript-Only Navigation
&lt;/h3&gt;

&lt;p&gt;Buttons and clickable containers should not replace anchors for navigation between indexable pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root-Level Client Components
&lt;/h3&gt;

&lt;p&gt;Placing &lt;code&gt;"use client"&lt;/code&gt; at a high level can expand the client-side dependency boundary and increase the JavaScript shipped to the browser. Interactive components should normally be isolated as deeply as practical.&lt;/p&gt;

&lt;p&gt;This is an architectural guideline, not a rule that every Client Component is harmful. For a deeper understanding of how AI systems interact with website content, read our analysis of &lt;a href="https://subodhkc.com/blog/what-is-llms-txt" rel="noopener noreferrer"&gt;llms.txt for AI agent discovery&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. A Server-First Next.js Pattern
&lt;/h2&gt;

&lt;p&gt;Public pages should resolve their essential content before returning the response.&lt;br&gt;
// app/products/[id]/page.tsx&lt;/p&gt;

&lt;p&gt;import type { Metadata } from 'next';&lt;br&gt;
import Link from 'next/link';&lt;br&gt;
import { notFound } from 'next/navigation';&lt;/p&gt;

&lt;p&gt;type Product = {&lt;br&gt;
  id: string;&lt;br&gt;
  name: string;&lt;br&gt;
  description: string;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;type ProductPageProps = {&lt;br&gt;
  params: Promise&amp;lt;{ id: string }&amp;gt;;&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;async function getProduct(id: string): Promise&amp;lt;Product | null&amp;gt; {&lt;br&gt;
  const response = await fetch(&lt;br&gt;
    &lt;code&gt;https://api.example.com/products/${encodeURIComponent(id)}&lt;/code&gt;,&lt;br&gt;
    {&lt;br&gt;
      next: { revalidate: 3600 },&lt;br&gt;
    }&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;if (response.status === 404) return null;&lt;br&gt;
  if (!response.ok) throw new Error('Unable to retrieve product');&lt;/p&gt;

&lt;p&gt;return response.json();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;export async function generateMetadata(&lt;br&gt;
  { params }: ProductPageProps&lt;br&gt;
): Promise&amp;lt;Metadata&amp;gt; {&lt;br&gt;
  const { id } = await params;&lt;br&gt;
  const product = await getProduct(id);&lt;/p&gt;

&lt;p&gt;if (!product) {&lt;br&gt;
    return {&lt;br&gt;
      title: 'Product not found',&lt;br&gt;
      robots: { index: false },&lt;br&gt;
    };&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;return {&lt;br&gt;
    title: &lt;code&gt;${product.name} | Example Company&lt;/code&gt;,&lt;br&gt;
    description: product.description.slice(0, 160),&lt;br&gt;
    alternates: {&lt;br&gt;
      canonical: &lt;code&gt;/products/${product.id}&lt;/code&gt;,&lt;br&gt;
    },&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;export default async function ProductPage({&lt;br&gt;
  params,&lt;br&gt;
}: ProductPageProps) {&lt;br&gt;
  const { id } = await params;&lt;br&gt;
  const product = await getProduct(id);&lt;/p&gt;

&lt;p&gt;if (!product) notFound();&lt;/p&gt;

&lt;p&gt;return (&lt;br&gt;
    &amp;lt;main&amp;gt;&lt;br&gt;
      &amp;lt;article&amp;gt;&lt;br&gt;
        &amp;lt;h1&amp;gt;{product.name}&amp;lt;/h1&amp;gt;&lt;br&gt;
        &amp;lt;p&amp;gt;{product.description}&amp;lt;/p&amp;gt;&lt;br&gt;
      &amp;lt;/article&amp;gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;amp;lt;Link href="https://subodhkc.com/products"&amp;amp;gt;
    View all products
  &amp;amp;lt;/Link&amp;amp;gt;
&amp;amp;lt;/main&amp;amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;);&lt;br&gt;
}&lt;br&gt;
Pages in the Next.js App Router are Server Components by default, while current dynamic-route parameters are provided asynchronously. Client Components can then be introduced for specific interactive functions without moving the page's primary content into the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The 2 MB Fetch Limit
&lt;/h2&gt;

&lt;p&gt;HTML size is a legitimate but uncommon risk.&lt;/p&gt;

&lt;p&gt;As of March 2026, Google states that Googlebot fetches up to 2 MB for an individual non-PDF URL, including response headers. Content beyond that limit is not retrieved, rendered or indexed. Referenced JavaScript and CSS resources are fetched separately and receive their own limits.&lt;/p&gt;

&lt;p&gt;This could become relevant when AI-generated code introduces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large inline JSON objects&lt;/li&gt;
&lt;li&gt;Base64-encoded images&lt;/li&gt;
&lt;li&gt;Extensive inline CSS&lt;/li&gt;
&lt;li&gt;Embedded scripts&lt;/li&gt;
&lt;li&gt;Oversized navigation structures&lt;/li&gt;
&lt;li&gt;Repeated generated markup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most normal pages will remain far below 2 MB. The limit should be presented as an edge case rather than a primary explanation for routine indexing delays.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. AI Search and llms.txt
&lt;/h2&gt;

&lt;p&gt;Server-readable content has value beyond conventional search because different crawlers and agents may possess different rendering capabilities. Google itself notes that not every bot can execute JavaScript.&lt;/p&gt;

&lt;p&gt;However, it is not currently defensible to claim that ChatGPT Search, Claude, Perplexity or all other AI systems categorically ignore JavaScript. Their official crawler documentation identifies crawler names, access controls and robots directives, but does not establish a universal rendering model.&lt;/p&gt;

&lt;p&gt;Similarly, &lt;code&gt;llms.txt&lt;/code&gt; should not be presented as an established AI-search ranking standard.&lt;/p&gt;

&lt;p&gt;The original &lt;code&gt;llms.txt&lt;/code&gt; project describes itself as a proposal intended to provide LLM-friendly site summaries and documentation links. For a complete analysis, see our detailed guide on &lt;a href="https://subodhkc.com/blog/what-is-llms-txt" rel="noopener noreferrer"&gt;what llms.txt is and why every AI-ready website should publish it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Google explicitly states that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Search does not use &lt;code&gt;llms.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It does not improve Google rankings.&lt;/li&gt;
&lt;li&gt;It does not improve visibility in Google's generative AI features.&lt;/li&gt;
&lt;li&gt;It may still be maintained for other tools that independently choose to use it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, &lt;code&gt;llms.txt&lt;/code&gt; may be treated as optional machine-readable documentation infrastructure following the &lt;a href="https://subodhkc.com/blog/what-is-llms-txt" rel="noopener noreferrer"&gt;llms.txt format specification&lt;/a&gt;, particularly for developer documentation or controlled agent workflows. It should not replace server rendering, crawlable navigation, structured content, sitemaps or conventional SEO.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. How to Test the Hypothesis Properly
&lt;/h2&gt;

&lt;p&gt;A credible paper must distinguish personal observation from measured evidence.&lt;/p&gt;

&lt;p&gt;The following controlled study would validate or reject the proposed relationship.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hypothesis
&lt;/h3&gt;

&lt;p&gt;Pages whose important content is absent from the initial HTML response will experience lower raw-HTML content visibility and may experience slower discovery or indexing than comparable server-rendered pages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Groups
&lt;/h3&gt;

&lt;p&gt;Create at least 20 matched page pairs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Group A:&lt;/strong&gt; Primary content server-rendered&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group B:&lt;/strong&gt; Identical content fetched after page load through JavaScript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep the following approximately equal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content length&lt;/li&gt;
&lt;li&gt;Topic&lt;/li&gt;
&lt;li&gt;Publication date&lt;/li&gt;
&lt;li&gt;URL depth&lt;/li&gt;
&lt;li&gt;Internal-link count&lt;/li&gt;
&lt;li&gt;Metadata&lt;/li&gt;
&lt;li&gt;Sitemap inclusion&lt;/li&gt;
&lt;li&gt;Canonical configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Measurements
&lt;/h3&gt;

&lt;p&gt;For every page, record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publication timestamp&lt;/li&gt;
&lt;li&gt;Initial HTML response&lt;/li&gt;
&lt;li&gt;Percentage of primary text present in raw HTML&lt;/li&gt;
&lt;li&gt;Number of internal links present in raw HTML&lt;/li&gt;
&lt;li&gt;First verified Googlebot server-log request&lt;/li&gt;
&lt;li&gt;Search Console crawl status&lt;/li&gt;
&lt;li&gt;First detected indexing date&lt;/li&gt;
&lt;li&gt;Rendered HTML reported by URL Inspection&lt;/li&gt;
&lt;li&gt;JavaScript bundle size&lt;/li&gt;
&lt;li&gt;LCP and INP measurements&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Evidence Threshold
&lt;/h3&gt;

&lt;p&gt;The paper should claim a causal indexing effect only if the server-rendered group consistently outperforms the client-only group while other variables remain controlled.&lt;/p&gt;

&lt;p&gt;Without this experiment, the strongest supportable conclusion is:&lt;/p&gt;

&lt;p&gt;Client-only rendering creates an avoidable crawlability dependency and should be treated as a technical SEO risk, particularly when public content depends on browser-side fetching or user interaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Practical Audit Checklist
&lt;/h2&gt;

&lt;p&gt;Developers should test every public route using the following controls:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl -L https://example.com/page&lt;/code&gt;Confirm that the response includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The primary heading&lt;/li&gt;
&lt;li&gt;Main page copy&lt;/li&gt;
&lt;li&gt;Canonical URL&lt;/li&gt;
&lt;li&gt;Page description&lt;/li&gt;
&lt;li&gt;Important internal links&lt;/li&gt;
&lt;li&gt;Structured data&lt;/li&gt;
&lt;li&gt;Indexing directives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additional validation should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Viewing the page with JavaScript disabled&lt;/li&gt;
&lt;li&gt;Comparing raw HTML with the rendered DOM&lt;/li&gt;
&lt;li&gt;Using Google Search Console URL Inspection&lt;/li&gt;
&lt;li&gt;Reviewing server logs for verified crawler activity&lt;/li&gt;
&lt;li&gt;Testing status codes and redirect chains&lt;/li&gt;
&lt;li&gt;Confirming robots and &lt;code&gt;noindex&lt;/code&gt; rules&lt;/li&gt;
&lt;li&gt;Checking canonical consistency&lt;/li&gt;
&lt;li&gt;Verifying sitemap inclusion&lt;/li&gt;
&lt;li&gt;Testing mobile and desktop output&lt;/li&gt;
&lt;li&gt;Confirming that pagination has stable URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;curl&lt;/code&gt; test is valuable, but it is not a complete crawler simulation. Google can render JavaScript, while raw &lt;code&gt;curl&lt;/code&gt; cannot. The test identifies dependence on rendering; it does not by itself prove that Google cannot index the page.&lt;/p&gt;

&lt;p&gt;For organizations building AI systems with compliance requirements, this audit should be integrated into your &lt;a href="https://subodhkc.com/guides" rel="noopener noreferrer"&gt;AI compliance governance workflow&lt;/a&gt;. If you need expert guidance on implementing server-first architectures, &lt;a href="https://subodhkc.com/contact" rel="noopener noreferrer"&gt;contact Subodh KC for advisory services&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;AI-assisted development has lowered the cost of producing sophisticated web interfaces. It has not removed the need for architectural review.&lt;/p&gt;

&lt;p&gt;The meaningful SEO risk is not AI-generated code itself, React, Next.js or the &lt;code&gt;"use client"&lt;/code&gt; directive. The risk is allowing critical public information to exist only after JavaScript executes, an API responds or a user performs an action.&lt;/p&gt;

&lt;p&gt;A server-first architecture provides a more resilient default:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Render primary public content on the server.&lt;/li&gt;
&lt;li&gt;Generate metadata through framework-supported server APIs.&lt;/li&gt;
&lt;li&gt;Use crawlable anchors for navigation.&lt;/li&gt;
&lt;li&gt;Give paginated content stable URLs.&lt;/li&gt;
&lt;li&gt;Isolate interactivity to focused components.&lt;/li&gt;
&lt;li&gt;Keep HTML and JavaScript payloads controlled.&lt;/li&gt;
&lt;li&gt;Verify what the server sends, not merely what the browser displays.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Server rendering will not guarantee indexing or rankings. It removes avoidable technical uncertainty and ensures that search engines, social preview systems and crawlers with varying capabilities receive a meaningful representation of the page.&lt;/p&gt;

&lt;p&gt;That is the defensible lesson from the case: AI coding assistants can accelerate implementation, but they should not be allowed to make rendering architecture decisions without explicit crawlability requirements. To learn more about how AI systems discover and process web content, explore our &lt;a href="https://subodhkc.com/blog/what-is-llms-txt" rel="noopener noreferrer"&gt;llms.txt implementation guide&lt;/a&gt; and our &lt;a href="https://subodhkc.com/services" rel="noopener noreferrer"&gt;AI architecture and governance services&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Google Search Central, &lt;em&gt;Understand the JavaScript SEO Basics&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Google Search Central, &lt;em&gt;Ask Google to Recrawl Your URLs&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Google Search Central, &lt;em&gt;How Google Search Works&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Google Search Central, &lt;em&gt;Inside Googlebot: Crawling, Fetching and the Bytes We Process&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Google Search Central, &lt;em&gt;Optimizing for Generative AI Features&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Next.js Documentation, &lt;em&gt;Server and Client Components&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Next.js Documentation, &lt;em&gt;Metadata and Dynamic Routes&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;OpenAI, &lt;em&gt;Overview of OpenAI Crawlers&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Perplexity, &lt;em&gt;Perplexity Crawlers&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;llms.txt Project, &lt;em&gt;The /llms.txt File Proposal&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://subodhkc.com/architecture-decision-master-sheet" rel="noopener noreferrer"&gt;Use the Architecture Decision Master Sheet&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://subodhkc.com/blog/hidden-seo-risk-ai-assisted-frontend-development" rel="noopener noreferrer"&gt;subodhkc.com&lt;/a&gt;. Follow for more on AI governance, enterprise architecture, and compliance engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>seoriskaiassisteddev</category>
      <category>clientsiderenderings</category>
      <category>nextjsseo</category>
      <category>reactseocrawlability</category>
    </item>
    <item>
      <title>Low-Latency AI Voice Pipelines with Modal, FastAPI, Postgres</title>
      <dc:creator>Subodh Kc</dc:creator>
      <pubDate>Tue, 06 Oct 2026 15:17:16 +0000</pubDate>
      <link>https://dev.to/subodhkc/low-latency-ai-voice-pipelines-with-modal-fastapi-postgres-8k4</link>
      <guid>https://dev.to/subodhkc/low-latency-ai-voice-pipelines-with-modal-fastapi-postgres-8k4</guid>
      <description>&lt;h3&gt;
  
  
  The hard part of a production voice agent is not connecting speech recognition to an LLM. It is deciding what must happen immediately, what can wait for reasoning, where session state belongs, and what the system should do when one dependency stops behaving.
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Updated August 8, 2026&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most AI voice tutorials end at approximately the same place.&lt;/p&gt;

&lt;p&gt;Audio comes in.&lt;/p&gt;

&lt;p&gt;Speech becomes text.&lt;/p&gt;

&lt;p&gt;The text goes to a language model.&lt;/p&gt;

&lt;p&gt;The model returns an answer.&lt;/p&gt;

&lt;p&gt;Text-to-speech turns the answer back into audio.&lt;/p&gt;

&lt;p&gt;It looks something like this:&lt;/p&gt;

&lt;p&gt;Caller&lt;br&gt;
   ↓&lt;br&gt;
Speech-to-Text&lt;br&gt;
   ↓&lt;br&gt;
LLM&lt;br&gt;
   ↓&lt;br&gt;
Text-to-Speech&lt;br&gt;
   ↓&lt;br&gt;
Caller&lt;/p&gt;

&lt;p&gt;That is enough to prove the concept.&lt;/p&gt;

&lt;p&gt;It is not enough to run a production voice system.&lt;/p&gt;

&lt;p&gt;The problems begin when several things happen at once.&lt;/p&gt;

&lt;p&gt;A caller interrupts the assistant while it is speaking.&lt;/p&gt;

&lt;p&gt;Another customer calls while the first model request is still running.&lt;/p&gt;

&lt;p&gt;One tenant has a calendar integration and another does not.&lt;/p&gt;

&lt;p&gt;The model provider slows down.&lt;/p&gt;

&lt;p&gt;The database is temporarily unreachable.&lt;/p&gt;

&lt;p&gt;An appointment API returns successfully but the acknowledgement is lost.&lt;/p&gt;

&lt;p&gt;A configuration changes while a call is already active.&lt;/p&gt;

&lt;p&gt;A new container starts cold.&lt;/p&gt;

&lt;p&gt;Someone asks a question that can be answered in 30 milliseconds, but the architecture sends it through a multi-second reasoning path anyway.&lt;/p&gt;

&lt;p&gt;The naive pipeline has only one route:&lt;/p&gt;

&lt;p&gt;everything&lt;br&gt;
    ↓&lt;br&gt;
   LLM&lt;/p&gt;

&lt;p&gt;A production architecture needs considerably more judgment.&lt;/p&gt;

&lt;p&gt;The architecture I have come to prefer separates the &lt;strong&gt;real-time media path&lt;/strong&gt;, the &lt;strong&gt;orchestration path&lt;/strong&gt;, and the &lt;strong&gt;persistent control plane&lt;/strong&gt;. It also treats graceful degradation as part of the normal design rather than something added after the first outage.&lt;/p&gt;

&lt;p&gt;The combination of &lt;strong&gt;FastAPI, Modal and Postgres/Supabase&lt;/strong&gt; can work well for this kind of system.&lt;/p&gt;

&lt;p&gt;But they should not be treated as three interchangeable pieces of a stack.&lt;/p&gt;

&lt;p&gt;FastAPI is the application and ASGI framework.&lt;/p&gt;

&lt;p&gt;Modal is the execution and scaling environment.&lt;/p&gt;

&lt;p&gt;Postgres is the durable system of record.&lt;/p&gt;

&lt;p&gt;Putting each one in the right part of the architecture matters more than simply having all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture I would not build
&lt;/h2&gt;

&lt;p&gt;A tempting first implementation looks like this:&lt;/p&gt;

&lt;p&gt;Incoming audio&lt;br&gt;
      ↓&lt;br&gt;
FastAPI WebSocket&lt;br&gt;
      ↓&lt;br&gt;
Write audio event to Postgres&lt;br&gt;
      ↓&lt;br&gt;
Load tenant configuration from Postgres&lt;br&gt;
      ↓&lt;br&gt;
Send transcript to LLM&lt;br&gt;
      ↓&lt;br&gt;
Write LLM response to Postgres&lt;br&gt;
      ↓&lt;br&gt;
Read latest state from Postgres&lt;br&gt;
      ↓&lt;br&gt;
Call booking tool&lt;br&gt;
      ↓&lt;br&gt;
Write result&lt;br&gt;
      ↓&lt;br&gt;
Generate speech&lt;/p&gt;

&lt;p&gt;It is logically clean.&lt;/p&gt;

&lt;p&gt;It is operationally expensive.&lt;/p&gt;

&lt;p&gt;Every additional round trip sits inside the user's conversational turn.&lt;/p&gt;

&lt;p&gt;That means database latency, model latency, tool latency and network latency all begin accumulating in series.&lt;/p&gt;

&lt;p&gt;A better mental model is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                REAL-TIME MEDIA PLANE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Caller&lt;br&gt;
   ↓&lt;br&gt;
Audio ingress&lt;br&gt;
   ↓&lt;br&gt;
Turn / speech detection&lt;br&gt;
   ↓&lt;br&gt;
Streaming transcription&lt;br&gt;
   ↓&lt;br&gt;
In-memory session orchestrator&lt;br&gt;
   ↓&lt;br&gt;
Routing decision&lt;br&gt;
   ↓&lt;br&gt;
Response generation&lt;br&gt;
   ↓&lt;br&gt;
Speech synthesis&lt;br&gt;
   ↓&lt;br&gt;
Caller&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                   CONTROL PLANE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Authentication&lt;br&gt;
Tenant resolution&lt;br&gt;
Agent configuration&lt;br&gt;
Tool permissions&lt;br&gt;
Fallback policy&lt;br&gt;
Rate limits&lt;br&gt;
Session policy&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  PERSISTENCE PLANE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Postgres / Supabase&lt;/p&gt;

&lt;p&gt;Tenant configuration&lt;br&gt;
Call/session record&lt;br&gt;
Configuration versions&lt;br&gt;
Tool execution ledger&lt;br&gt;
Transcript/event history&lt;br&gt;
Bookings / outcomes&lt;br&gt;
Audit metadata&lt;/p&gt;

&lt;p&gt;The key idea is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Postgres should know what happened. It does not need to sit between every two milliseconds of what is happening.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That distinction alone can remove a surprising amount of unnecessary latency.&lt;/p&gt;

&lt;h2&gt;
  
  
  What FastAPI should do in this architecture
&lt;/h2&gt;

&lt;p&gt;FastAPI is well suited to the orchestration boundary because it sits on ASGI and supports both normal HTTP endpoints and WebSockets.&lt;/p&gt;

&lt;p&gt;FastAPI's own documentation supports WebSocket routes directly. (&lt;a href="https://fastapi.tiangolo.com/advanced/websockets/" rel="noopener noreferrer"&gt;fastapi.tiangolo.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;For a voice platform, I would typically use FastAPI for several different interface types:&lt;/p&gt;

&lt;p&gt;HTTP webhooks&lt;br&gt;
    |&lt;br&gt;
    +-- call started&lt;br&gt;
    +-- call completed&lt;br&gt;
    +-- recording available&lt;br&gt;
    +-- provider status&lt;br&gt;
    +-- booking callback&lt;/p&gt;

&lt;p&gt;WebSockets&lt;br&gt;
    |&lt;br&gt;
    +-- media stream&lt;br&gt;
    +-- browser voice session&lt;br&gt;
    +-- live transcript / state&lt;/p&gt;

&lt;p&gt;Normal APIs&lt;br&gt;
    |&lt;br&gt;
    +-- configuration&lt;br&gt;
    +-- session history&lt;br&gt;
    +-- tools&lt;br&gt;
    +-- admin actions&lt;/p&gt;

&lt;p&gt;The important word here is &lt;strong&gt;orchestration&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;FastAPI should not become the place where every expensive computation is performed synchronously.&lt;/p&gt;

&lt;p&gt;If your WebSocket event loop is also doing CPU-heavy processing, blocking model SDK calls and large database operations, the gateway that is supposed to coordinate the conversation can become the bottleneck itself.&lt;/p&gt;

&lt;p&gt;That is the same architectural problem I discussed in the context of &lt;a href="https://subodhkc.com/blog/fastapi-websocket-1006-llm-streaming-disconnect" rel="noopener noreferrer"&gt;FastAPI WebSocket 1006 failures&lt;/a&gt;: slow upstream work and an event loop that has actually stopped making progress are different failure classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Modal fits
&lt;/h2&gt;

&lt;p&gt;Modal changes the deployment model.&lt;/p&gt;

&lt;p&gt;It can serve a FastAPI application directly as an ASGI app:&lt;/p&gt;

&lt;p&gt;@modal.asgi_app()&lt;br&gt;
def fastapi_app():&lt;br&gt;
    return web_app&lt;/p&gt;

&lt;p&gt;Modal's current documentation specifically supports FastAPI and other ASGI applications, and its ASGI-hosted functions support WebSockets. (&lt;a href="https://modal.com/docs/guide/webhooks" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That matters for AI voice because the same environment can also give you access to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;autoscaled compute;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CPU and GPU functions;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;secrets;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;container lifecycle hooks;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;concurrency controls;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;warm containers;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;region selection;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;independently scaled functions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But "serverless" does not mean "latency disappears."&lt;/p&gt;

&lt;p&gt;There are several Modal behaviors I would account for explicitly before putting a voice path into production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cold starts are part of the latency budget
&lt;/h2&gt;

&lt;p&gt;When no ready container exists for a Modal Function, Modal starts one.&lt;/p&gt;

&lt;p&gt;Modal calls this a cold start and identifies two main sources of additional latency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;waiting for a container to become available;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;initialization work inside the newly started container.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(&lt;a href="https://modal.com/docs/guide/cold-start" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;For a batch job, that can be irrelevant.&lt;/p&gt;

&lt;p&gt;For voice, the user is already waiting.&lt;/p&gt;

&lt;p&gt;Current Modal exposes three particularly useful controls:&lt;/p&gt;

&lt;p&gt;min_containers&lt;/p&gt;

&lt;p&gt;buffer_containers&lt;/p&gt;

&lt;p&gt;scaledown_window&lt;/p&gt;

&lt;p&gt;&lt;code&gt;min_containers&lt;/code&gt; maintains a warm baseline.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;buffer_containers&lt;/code&gt; gives the autoscaler spare capacity while traffic is active.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;scaledown_window&lt;/code&gt; controls how long idle containers remain alive before scaling down; the current default maximum idle period is 60 seconds if you do not change it. (&lt;a href="https://modal.com/docs/guide/cold-start" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;There is a direct tradeoff:&lt;/p&gt;

&lt;p&gt;more warm capacity&lt;br&gt;
        ↓&lt;br&gt;
lower cold-start probability&lt;br&gt;
        ↓&lt;br&gt;
higher idle cost&lt;/p&gt;

&lt;p&gt;There is no correct universal number.&lt;/p&gt;

&lt;p&gt;For a production voice system, I would derive it from call-arrival patterns and P95/P99 startup latency rather than setting &lt;code&gt;min_containers=10&lt;/code&gt; because somebody's tutorial did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modal WebSockets have one non-obvious consequence
&lt;/h2&gt;

&lt;p&gt;Modal documents a WebSocket connection as a &lt;strong&gt;single Function call for the lifetime of that connection&lt;/strong&gt;. It also supports concurrent inputs so multiple WebSockets can be handled by the same container. (&lt;a href="https://modal.com/docs/guide/webhooks" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That has an operational consequence.&lt;/p&gt;

&lt;p&gt;Modal Functions currently have a default execution timeout of &lt;strong&gt;300 seconds&lt;/strong&gt;, although the timeout can be configured from one second up to 24 hours. (&lt;a href="https://modal.com/docs/guide/timeouts" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;So if your WebSocket represents a phone call that may reasonably last longer than five minutes, I would not leave the Function timeout implicit.&lt;/p&gt;

&lt;p&gt;I would configure it deliberately.&lt;/p&gt;

&lt;p&gt;The exact value depends on your call/session policy.&lt;/p&gt;

&lt;p&gt;A 30-minute maximum conversation and a 24-hour browser connection are very different products.&lt;/p&gt;

&lt;h2&gt;
  
  
  A production-shaped Modal + FastAPI entry point
&lt;/h2&gt;

&lt;p&gt;A simplified version could look like this:&lt;/p&gt;

&lt;p&gt;import modal&lt;/p&gt;

&lt;p&gt;from fastapi import FastAPI, WebSocket, WebSocketDisconnect&lt;/p&gt;

&lt;p&gt;app = modal.App('voice-orchestrator')&lt;/p&gt;

&lt;p&gt;image = (&lt;br&gt;
    modal.Image.debian_slim()&lt;br&gt;
    .pip_install('fastapi[standard]')&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;web_app = FastAPI()&lt;/p&gt;

&lt;p&gt;@web_app.websocket('/v1/voice')&lt;br&gt;
async def voice_session(websocket: WebSocket):&lt;br&gt;
    await websocket.accept()&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try:
    while True:
        event = await websocket.receive()

        # Decode and route the event.
        # Keep expensive/blocking work off the ASGI loop.

except WebSocketDisconnect:
    pass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;@app.function(&lt;br&gt;
    image=image,&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Illustrative values only.
# Benchmark these for your workload.
min_containers=1,
buffer_containers=1,
scaledown_window=300,

# Explicitly choose a session lifetime appropriate
# for your product rather than depending on defaults.
timeout=3600,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;)&lt;br&gt;
@modal.concurrent(&lt;br&gt;
    max_inputs=20,&lt;br&gt;
    target_inputs=10,&lt;br&gt;
)&lt;br&gt;
@modal.asgi_app()&lt;br&gt;
def api():&lt;br&gt;
    return web_app&lt;/p&gt;

&lt;p&gt;The numbers above are examples, not production recommendations.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;20&lt;/code&gt; simultaneous WebSocket sessions may be trivial for one workload and disastrous for another.&lt;/p&gt;

&lt;p&gt;Modal's concurrency documentation makes the underlying behavior clear: individual containers can handle multiple inputs concurrently, while demand beyond the configured level causes Modal to scale additional containers. &lt;code&gt;target_inputs&lt;/code&gt; can also influence when that scale-out begins so traffic is less likely to wait for cold containers. (&lt;a href="https://modal.com/docs/guide/concurrent-inputs" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Benchmark the work that actually happens inside your connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Geography matters more than many voice-agent diagrams admit
&lt;/h2&gt;

&lt;p&gt;This is easy to overlook.&lt;/p&gt;

&lt;p&gt;Suppose:&lt;/p&gt;

&lt;p&gt;Caller           Dallas&lt;br&gt;
Modal runtime    Oregon&lt;br&gt;
Postgres         Virginia&lt;br&gt;
LLM              another region&lt;br&gt;
TTS              another region&lt;/p&gt;

&lt;p&gt;No individual component may be "slow."&lt;/p&gt;

&lt;p&gt;The architecture has simply created several cross-country round trips inside each conversational turn.&lt;/p&gt;

&lt;p&gt;Modal currently routes Function inputs through &lt;code&gt;us-east&lt;/code&gt; by default, although it supports compute-region selection and now has regional routing in beta for selected routing regions. Modal specifically calls out latency-sensitive applications and proximity to external databases as reasons to control region placement. (&lt;a href="https://modal.com/docs/guide/region-selection" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Supabase also deploys a project into a primary region and recommends choosing a region close to users for performance. (&lt;a href="https://supabase.com/docs/guides/platform/regions" rel="noopener noreferrer"&gt;supabase.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;For voice, I would draw the physical architecture, not only the logical one.&lt;/p&gt;

&lt;p&gt;USER REGION&lt;br&gt;
     |&lt;br&gt;
     v&lt;br&gt;
MEDIA INGRESS&lt;br&gt;
     |&lt;br&gt;
     v&lt;br&gt;
ORCHESTRATOR REGION&lt;br&gt;
     |&lt;br&gt;
     +------ POSTGRES REGION&lt;br&gt;
     |&lt;br&gt;
     +------ STT REGION&lt;br&gt;
     |&lt;br&gt;
     +------ LLM REGION&lt;br&gt;
     |&lt;br&gt;
     +------ TTS REGION&lt;/p&gt;

&lt;p&gt;Then measure the actual network legs.&lt;/p&gt;

&lt;p&gt;Model benchmarks do not include the latency you created by placing the system three regions apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Postgres should be the memory of the system, not its nervous system
&lt;/h2&gt;

&lt;p&gt;Postgres is excellent at things a voice application needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;tenant configuration;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;durable call records;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;tool execution history;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;bookings;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;agent versions;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;permissions;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transcripts;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;audit events;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;analytics.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is much less attractive as a per-audio-frame message bus.&lt;/p&gt;

&lt;p&gt;I do not want this:&lt;/p&gt;

&lt;p&gt;audio frame&lt;br&gt;
   ↓&lt;br&gt;
INSERT&lt;br&gt;
   ↓&lt;br&gt;
audio frame&lt;br&gt;
   ↓&lt;br&gt;
INSERT&lt;br&gt;
   ↓&lt;br&gt;
token&lt;br&gt;
   ↓&lt;br&gt;
INSERT&lt;/p&gt;

&lt;p&gt;inside the live turn.&lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;p&gt;Real-time state&lt;br&gt;
    =&lt;br&gt;
in-memory session state&lt;/p&gt;

&lt;p&gt;Durable state&lt;br&gt;
    =&lt;br&gt;
Postgres&lt;/p&gt;

&lt;p&gt;Then persist at useful boundaries:&lt;/p&gt;

&lt;p&gt;session started&lt;/p&gt;

&lt;p&gt;turn completed&lt;/p&gt;

&lt;p&gt;tool requested&lt;/p&gt;

&lt;p&gt;tool succeeded / failed&lt;/p&gt;

&lt;p&gt;booking confirmed&lt;/p&gt;

&lt;p&gt;fallback activated&lt;/p&gt;

&lt;p&gt;session ended&lt;/p&gt;

&lt;p&gt;You can still stream operational state elsewhere when required.&lt;/p&gt;

&lt;p&gt;Just do not make the database a mandatory hop for every audio or token event unless you have a specific reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supabase changes the Postgres operational model, but not the principle
&lt;/h2&gt;

&lt;p&gt;Supabase gives each project a full Postgres database and layers products such as Auth, Realtime and Storage around it. (&lt;a href="https://supabase.com/docs/guides/database/overview" rel="noopener noreferrer"&gt;supabase.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;For backend connections, Supabase currently recommends different connection modes depending on workload:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;direct connections for long-lived backend clients where connectivity permits;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;session-mode pooling for persistent backends on IPv4-only networks;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transaction-mode pooling for temporary/serverless-style connections;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;dedicated transaction pooling as another option on applicable plans.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(&lt;a href="https://supabase.com/docs/guides/database/connecting-to-postgres" rel="noopener noreferrer"&gt;supabase.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That distinction matters with an autoscaling Modal application.&lt;/p&gt;

&lt;p&gt;A small number of warm, long-lived containers behaves differently from hundreds of containers appearing and disappearing during a spike.&lt;/p&gt;

&lt;p&gt;Do not let every new container create an uncontrolled collection of Postgres connections.&lt;/p&gt;

&lt;p&gt;Capacity-plan the database as part of the autoscaling design.&lt;/p&gt;

&lt;h2&gt;
  
  
  The multi-tenant problem starts before the first query
&lt;/h2&gt;

&lt;p&gt;In a SaaS voice system, a call does not simply belong to "the application."&lt;/p&gt;

&lt;p&gt;It belongs to a tenant.&lt;/p&gt;

&lt;p&gt;So the very beginning of the session should resolve:&lt;/p&gt;

&lt;p&gt;connection&lt;br&gt;
     ↓&lt;br&gt;
trusted channel / number / token&lt;br&gt;
     ↓&lt;br&gt;
organization&lt;br&gt;
     ↓&lt;br&gt;
voice agent&lt;br&gt;
     ↓&lt;br&gt;
approved configuration version&lt;br&gt;
     ↓&lt;br&gt;
tool permissions&lt;/p&gt;

&lt;p&gt;I do not want arbitrary downstream code repeatedly accepting:&lt;/p&gt;

&lt;p&gt;organization_id = request.organization_id&lt;/p&gt;

&lt;p&gt;and trusting it because the caller supplied one.&lt;/p&gt;

&lt;p&gt;The tenant boundary should be established from authenticated or otherwise trusted context.&lt;/p&gt;

&lt;p&gt;Then it should follow the session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put tenant identity on the durable records
&lt;/h2&gt;

&lt;p&gt;A minimal relational model might include:&lt;/p&gt;

&lt;p&gt;organizations&lt;/p&gt;

&lt;p&gt;organization_members&lt;/p&gt;

&lt;p&gt;voice_agents&lt;br&gt;
    organization_id&lt;/p&gt;

&lt;p&gt;voice_agent_versions&lt;br&gt;
    organization_id&lt;/p&gt;

&lt;p&gt;phone_channels&lt;br&gt;
    organization_id&lt;/p&gt;

&lt;p&gt;call_sessions&lt;br&gt;
    organization_id&lt;br&gt;
    voice_agent_version_id&lt;/p&gt;

&lt;p&gt;conversation_turns&lt;br&gt;
    organization_id&lt;br&gt;
    call_session_id&lt;/p&gt;

&lt;p&gt;tool_executions&lt;br&gt;
    organization_id&lt;br&gt;
    call_session_id&lt;/p&gt;

&lt;p&gt;bookings&lt;br&gt;
    organization_id&lt;/p&gt;

&lt;p&gt;For user-facing access, Supabase's Postgres Row Level Security can enforce authorization directly at the database row boundary.&lt;/p&gt;

&lt;p&gt;Supabase recommends enabling RLS on exposed schemas and describes policies as effectively adding authorization predicates to every query. (&lt;a href="https://supabase.com/docs/guides/database/postgres/row-level-security" rel="noopener noreferrer"&gt;supabase.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;A simplified membership policy could look like:&lt;/p&gt;

&lt;p&gt;create policy 'members can read organization calls'&lt;br&gt;
on call_sessions&lt;br&gt;
for select&lt;br&gt;
to authenticated&lt;br&gt;
using (&lt;br&gt;
    exists (&lt;br&gt;
        select 1&lt;br&gt;
        from organization_members m&lt;br&gt;
        where m.organization_id = call_sessions.organization_id&lt;br&gt;
          and m.user_id = auth.uid()&lt;br&gt;
    )&lt;br&gt;
);&lt;/p&gt;

&lt;p&gt;The exact policy needs to match your schema and indexes.&lt;/p&gt;

&lt;p&gt;The important point is that the tenant boundary is not merely a React filter.&lt;/p&gt;

&lt;p&gt;For a deeper treatment of multi-tenant data isolation patterns, see &lt;a href="https://subodhkc.com/blog/implementing-rag-row-level-security-for-multi-tenant-ai" rel="noopener noreferrer"&gt;implementing RAG row-level security for multi-tenant AI&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Supabase detail can undo an otherwise good tenancy design
&lt;/h2&gt;

&lt;p&gt;Server-side Supabase credentials deserve special attention.&lt;/p&gt;

&lt;p&gt;Supabase explicitly documents that a client using the service-role authorization bypasses Row Level Security. Its newer secret-key model serves the same category of trusted backend use and must not be exposed to customers. (&lt;a href="https://supabase.com/docs/guides/troubleshooting/why-is-my-service-role-key-client-getting-rls-errors-or-not-returning-data-7_1K9z" rel="noopener noreferrer"&gt;supabase.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;So:&lt;/p&gt;

&lt;p&gt;RLS enabled&lt;/p&gt;

&lt;p&gt;does not automatically mean:&lt;/p&gt;

&lt;p&gt;every backend query is tenant-isolated&lt;/p&gt;

&lt;p&gt;If your trusted voice backend operates with a credential that bypasses RLS, &lt;strong&gt;your application code now owns tenant authorization for those queries&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is a major architectural boundary.&lt;/p&gt;

&lt;p&gt;I prefer either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;scoped database access where RLS remains meaningful; or&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;an explicit trusted-backend authorization layer that validates the resolved tenant before every privileged operation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What I would not do is assume the word "RLS" somewhere in the architecture means the multi-tenant problem is solved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Snapshot the voice-agent configuration when the call begins
&lt;/h2&gt;

&lt;p&gt;This is a small design choice that becomes extremely important later.&lt;/p&gt;

&lt;p&gt;consider a call begins at 10:02.&lt;/p&gt;

&lt;p&gt;At 10:04 an administrator changes:&lt;/p&gt;

&lt;p&gt;system prompt&lt;/p&gt;

&lt;p&gt;business hours&lt;/p&gt;

&lt;p&gt;transfer policy&lt;/p&gt;

&lt;p&gt;model&lt;/p&gt;

&lt;p&gt;temperature&lt;/p&gt;

&lt;p&gt;booking rules&lt;/p&gt;

&lt;p&gt;Should the call that started at 10:02 immediately inherit all of those changes?&lt;/p&gt;

&lt;p&gt;Sometimes yes.&lt;/p&gt;

&lt;p&gt;Usually I would rather have a consistent session.&lt;/p&gt;

&lt;p&gt;At admission:&lt;/p&gt;

&lt;p&gt;call begins&lt;br&gt;
    ↓&lt;br&gt;
resolve tenant&lt;br&gt;
    ↓&lt;br&gt;
resolve active agent version&lt;br&gt;
    ↓&lt;br&gt;
snapshot configuration&lt;br&gt;
    ↓&lt;br&gt;
session uses version 184&lt;/p&gt;

&lt;p&gt;Persist:&lt;/p&gt;

&lt;p&gt;call_session.voice_agent_version_id = 184&lt;/p&gt;

&lt;p&gt;Now when someone asks:&lt;/p&gt;

&lt;p&gt;Why did the assistant say this?&lt;/p&gt;

&lt;p&gt;you can answer with the configuration that actually governed that call.&lt;/p&gt;

&lt;p&gt;Not whatever happens to be current today.&lt;/p&gt;

&lt;p&gt;This is as useful for debugging as it is for governance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three-layer routing pipeline
&lt;/h2&gt;

&lt;p&gt;This is the part of the architecture that made the largest conceptual difference for me.&lt;/p&gt;

&lt;p&gt;A voice turn should not automatically enter the most expensive and least predictable reasoning path.&lt;/p&gt;

&lt;p&gt;I think of the router in three layers.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 USER EVENT
                     |
                     v
          +---------------------+
          | 1. SESSION CONTROL  |
          +----------+----------+
                     |
                not handled
                     v
          +---------------------+
          | 2. BOUNDED FAST PATH|
          +----------+----------+
                     |
                not handled
                     v
          +---------------------+
          | 3. LLM REASONING    |
          +---------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is an architecture pattern, not a feature supplied automatically by FastAPI, Modal or Postgres.&lt;/p&gt;

&lt;p&gt;Each layer exists for a different reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: deterministic session controls
&lt;/h2&gt;

&lt;p&gt;Some events should never wait for an LLM.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;p&gt;WebSocket disconnected&lt;/p&gt;

&lt;p&gt;call ended&lt;/p&gt;

&lt;p&gt;DTMF event&lt;/p&gt;

&lt;p&gt;hard timeout reached&lt;/p&gt;

&lt;p&gt;authentication failed&lt;/p&gt;

&lt;p&gt;session cancelled&lt;/p&gt;

&lt;p&gt;explicit tool acknowledgement&lt;/p&gt;

&lt;p&gt;provider status event&lt;/p&gt;

&lt;p&gt;rate limit exceeded&lt;/p&gt;

&lt;p&gt;These are state-machine events.&lt;/p&gt;

&lt;p&gt;They belong in deterministic application logic.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;if event.type == 'disconnect':&lt;br&gt;
    await close_session()&lt;br&gt;
    return&lt;/p&gt;

&lt;p&gt;if event.type == 'dtmf' and event.digit == '0':&lt;br&gt;
    await request_human_transfer()&lt;br&gt;
    return&lt;/p&gt;

&lt;p&gt;if session.deadline_expired():&lt;br&gt;
    await initiate_timeout_flow()&lt;br&gt;
    return&lt;/p&gt;

&lt;p&gt;One qualification matters here.&lt;/p&gt;

&lt;p&gt;A caller saying:&lt;/p&gt;

&lt;p&gt;"Stop."&lt;/p&gt;

&lt;p&gt;is not automatically deterministic if you are relying on speech recognition to detect the word.&lt;/p&gt;

&lt;p&gt;STT itself may be probabilistic.&lt;/p&gt;

&lt;p&gt;The deterministic piece starts &lt;strong&gt;after&lt;/strong&gt; the event has been classified into a trusted session command.&lt;/p&gt;

&lt;p&gt;That sounds pedantic until a voice agent hangs up because it hallucinated the word "goodbye."&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: the bounded fast path
&lt;/h2&gt;

&lt;p&gt;A large fraction of customer interactions do not require general reasoning.&lt;/p&gt;

&lt;p&gt;Examples might include:&lt;/p&gt;

&lt;p&gt;What time do you close?&lt;/p&gt;

&lt;p&gt;What is the address?&lt;/p&gt;

&lt;p&gt;Do you service this ZIP code?&lt;/p&gt;

&lt;p&gt;Do you have my appointment?&lt;/p&gt;

&lt;p&gt;Can I leave a callback number?&lt;/p&gt;

&lt;p&gt;What is the status of my booking?&lt;/p&gt;

&lt;p&gt;Some of those can be answered from authoritative structured data.&lt;/p&gt;

&lt;p&gt;A good fast path might use:&lt;/p&gt;

&lt;p&gt;known command&lt;br&gt;
      ↓&lt;br&gt;
validated lookup&lt;br&gt;
      ↓&lt;br&gt;
templated response&lt;/p&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;p&gt;retrieval&lt;br&gt;
    ↓&lt;br&gt;
high-confidence approved content&lt;br&gt;
    ↓&lt;br&gt;
short response&lt;/p&gt;

&lt;p&gt;instead of:&lt;/p&gt;

&lt;p&gt;construct massive context&lt;br&gt;
        ↓&lt;br&gt;
send to strongest reasoning model&lt;br&gt;
        ↓&lt;br&gt;
wait&lt;/p&gt;

&lt;p&gt;The goal is not to avoid LLMs.&lt;/p&gt;

&lt;p&gt;It is to avoid paying reasoning latency for a problem that does not require reasoning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fast path does not mean "hard-code every conversation"
&lt;/h2&gt;

&lt;p&gt;There is an easy mistake to make here.&lt;/p&gt;

&lt;p&gt;A developer hears "deterministic fast path" and creates:&lt;/p&gt;

&lt;p&gt;if 'hours' in transcript:&lt;br&gt;
    return BUSINESS_HOURS&lt;/p&gt;

&lt;p&gt;That becomes fragile almost immediately.&lt;/p&gt;

&lt;p&gt;Natural language still needs interpretation.&lt;/p&gt;

&lt;p&gt;The architecture should distinguish:&lt;/p&gt;

&lt;p&gt;bounded answer source&lt;/p&gt;

&lt;p&gt;from:&lt;/p&gt;

&lt;p&gt;perfectly deterministic intent recognition&lt;/p&gt;

&lt;p&gt;The source of truth can be deterministic while the route into it uses a classifier, a small model or semantic matching.&lt;/p&gt;

&lt;p&gt;For higher-impact actions, require stronger evidence.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;"What time do you close?"&lt;br&gt;
     ↓&lt;br&gt;
low-risk FAQ fast path&lt;/p&gt;

&lt;p&gt;is very different from:&lt;/p&gt;

&lt;p&gt;"Cancel every appointment I have."&lt;br&gt;
     ↓&lt;br&gt;
identity + explicit confirmation&lt;br&gt;
     ↓&lt;br&gt;
authorized tool&lt;/p&gt;

&lt;p&gt;Latency optimization must not erase authorization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: real-time LLM reasoning
&lt;/h2&gt;

&lt;p&gt;The general reasoning path is where the LLM earns its cost.&lt;/p&gt;

&lt;p&gt;This is the route for turns requiring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;synthesis across several facts;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ambiguous conversational context;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;multi-step planning;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;tool selection;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;qualification;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;complex natural-language explanation.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The LLM should receive enough context to work.&lt;/p&gt;

&lt;p&gt;Not every object the company has ever stored.&lt;/p&gt;

&lt;p&gt;A good request envelope might contain:&lt;/p&gt;

&lt;p&gt;tenant policy&lt;/p&gt;

&lt;p&gt;agent configuration version&lt;/p&gt;

&lt;p&gt;conversation state&lt;/p&gt;

&lt;p&gt;bounded relevant history&lt;/p&gt;

&lt;p&gt;retrieved knowledge&lt;/p&gt;

&lt;p&gt;allowed tools&lt;/p&gt;

&lt;p&gt;authorization context&lt;/p&gt;

&lt;p&gt;deadline&lt;/p&gt;

&lt;p&gt;fallback policy&lt;/p&gt;

&lt;p&gt;Notice what is in that list:&lt;/p&gt;

&lt;p&gt;deadline&lt;/p&gt;

&lt;p&gt;The reasoning engine does not own unlimited conversational time simply because the WebSocket is still open.&lt;/p&gt;

&lt;h2&gt;
  
  
  A routing function should return more than an answer
&lt;/h2&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;from dataclasses import dataclass&lt;br&gt;
from enum import Enum&lt;/p&gt;

&lt;p&gt;class Route(Enum):&lt;br&gt;
    CONTROL = 'control'&lt;br&gt;
    FAST = 'fast'&lt;br&gt;
    REASONING = 'reasoning'&lt;/p&gt;

&lt;p&gt;@dataclass&lt;br&gt;
class RouteDecision:&lt;br&gt;
    route: Route&lt;br&gt;
    reason: str&lt;br&gt;
    confidence: float | None = None&lt;/p&gt;

&lt;p&gt;async def choose_route(event, session) -&amp;gt; RouteDecision:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;control = match_session_control(event, session)

if control:
    return RouteDecision(
        route=Route.CONTROL,
        reason=control,
    )

fast_match = await find_bounded_answer(
    event=event,
    session=session,
)

if fast_match.is_acceptable:
    return RouteDecision(
        route=Route.FAST,
        reason=fast_match.source,
        confidence=fast_match.confidence,
    )

return RouteDecision(
    route=Route.REASONING,
    reason=&amp;amp;#39;requires_general_reasoning&amp;amp;#39;,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The important thing is not this exact Python.&lt;/p&gt;

&lt;p&gt;It is making the route &lt;strong&gt;observable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Later you can measure:&lt;/p&gt;

&lt;p&gt;What percentage of turns use the fast path?&lt;/p&gt;

&lt;p&gt;How often does the fast path fall back?&lt;/p&gt;

&lt;p&gt;Which route produces poor outcomes?&lt;/p&gt;

&lt;p&gt;How much latency does each route add?&lt;/p&gt;

&lt;p&gt;Without an explicit route decision, "the AI was slow" is almost impossible to decompose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not put Postgres in the routing hot loop unless the query is necessary
&lt;/h2&gt;

&lt;p&gt;Suppose the fast path requires:&lt;/p&gt;

&lt;p&gt;business opening hours&lt;/p&gt;

&lt;p&gt;Do you need to query Postgres every time the caller asks?&lt;/p&gt;

&lt;p&gt;Probably not.&lt;/p&gt;

&lt;p&gt;You can load the tenant configuration when the session begins:&lt;/p&gt;

&lt;p&gt;session started&lt;br&gt;
      ↓&lt;br&gt;
load immutable config snapshot&lt;br&gt;
      ↓&lt;br&gt;
keep in memory&lt;br&gt;
      ↓&lt;br&gt;
serve repeated bounded reads&lt;/p&gt;

&lt;p&gt;For data that genuinely changes during the call, query the authoritative source.&lt;/p&gt;

&lt;p&gt;The rule I use is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;cache configuration; verify transactions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Business hours can be cached.&lt;/p&gt;

&lt;p&gt;Whether a specific appointment slot is still available should probably come from the scheduling authority.&lt;/p&gt;

&lt;p&gt;Those are different consistency requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  The most dangerous latency optimization is caching the wrong truth
&lt;/h2&gt;

&lt;p&gt;consider this:&lt;/p&gt;

&lt;p&gt;10:00:00&lt;br&gt;
availability = 2 PM&lt;/p&gt;

&lt;p&gt;10:00:05&lt;br&gt;
another customer books 2 PM&lt;/p&gt;

&lt;p&gt;10:00:08&lt;br&gt;
voice agent uses cached availability&lt;/p&gt;

&lt;p&gt;10:00:10&lt;br&gt;
"You're booked for 2 PM."&lt;/p&gt;

&lt;p&gt;Fast.&lt;/p&gt;

&lt;p&gt;Wrong.&lt;/p&gt;

&lt;p&gt;For transactional actions, the authoritative tool must win.&lt;/p&gt;

&lt;p&gt;The LLM can suggest:&lt;/p&gt;

&lt;p&gt;2 PM looks available&lt;/p&gt;

&lt;p&gt;but your system should only say:&lt;/p&gt;

&lt;p&gt;You're booked for 2 PM&lt;/p&gt;

&lt;p&gt;after the authoritative booking operation succeeds.&lt;/p&gt;

&lt;p&gt;That distinction is not about latency.&lt;/p&gt;

&lt;p&gt;It is about truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side effects need idempotency
&lt;/h2&gt;

&lt;p&gt;Voice systems are full of retries.&lt;/p&gt;

&lt;p&gt;Networks fail.&lt;/p&gt;

&lt;p&gt;Providers retry webhooks.&lt;/p&gt;

&lt;p&gt;Users repeat themselves.&lt;/p&gt;

&lt;p&gt;A model retries a tool.&lt;/p&gt;

&lt;p&gt;The booking succeeds, but the acknowledgement times out.&lt;/p&gt;

&lt;p&gt;Now the orchestrator thinks it failed.&lt;/p&gt;

&lt;p&gt;If you blindly retry:&lt;/p&gt;

&lt;p&gt;book 2 PM&lt;/p&gt;

&lt;p&gt;you may create two appointments.&lt;/p&gt;

&lt;p&gt;For side-effecting tools, I want:&lt;/p&gt;

&lt;p&gt;session_id&lt;br&gt;
turn_id&lt;br&gt;
tool_name&lt;br&gt;
idempotency_key&lt;br&gt;
request_hash&lt;br&gt;
status&lt;br&gt;
external_reference&lt;/p&gt;

&lt;p&gt;persisted in a durable execution ledger.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;create table tool_executions (&lt;br&gt;
    id uuid primary key,&lt;br&gt;
    organization_id uuid not null,&lt;br&gt;
    call_session_id uuid not null,&lt;br&gt;
    idempotency_key text not null,&lt;br&gt;
    tool_name text not null,&lt;br&gt;
    status text not null,&lt;br&gt;
    external_reference text,&lt;br&gt;
    created_at timestamptz not null default now(),&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unique (organization_id, idempotency_key)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;);&lt;/p&gt;

&lt;p&gt;Now a retry can ask:&lt;/p&gt;

&lt;p&gt;Did this logical operation already execute?&lt;/p&gt;

&lt;p&gt;before reproducing the side effect.&lt;/p&gt;

&lt;p&gt;Graceful degradation without idempotency can create a more serious incident than the one you were trying to survive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four levels of graceful degradation
&lt;/h2&gt;

&lt;p&gt;A production voice system should have an answer to:&lt;/p&gt;

&lt;p&gt;What happens when the LLM is unavailable?&lt;/p&gt;

&lt;p&gt;"Return HTTP 500" is not a conversational strategy.&lt;/p&gt;

&lt;p&gt;I prefer explicit degraded modes.&lt;/p&gt;

&lt;p&gt;Normal operation might look like:&lt;/p&gt;

&lt;p&gt;NORMAL&lt;/p&gt;

&lt;p&gt;Primary reasoning&lt;br&gt;
RAG&lt;br&gt;
Tools&lt;br&gt;
Full conversation policy&lt;/p&gt;

&lt;p&gt;If that path fails, move through controlled levels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Degradation Level 1: fail over the reasoning provider
&lt;/h2&gt;

&lt;p&gt;The primary model is unavailable or exceeds its deadline.&lt;/p&gt;

&lt;p&gt;If your risk, privacy and commercial requirements permit it:&lt;/p&gt;

&lt;p&gt;Primary model&lt;br&gt;
      X&lt;br&gt;
      ↓&lt;br&gt;
Secondary approved model/provider&lt;/p&gt;

&lt;p&gt;The important word is &lt;strong&gt;approved&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A fallback provider should not suddenly receive data your organization deliberately prohibited from leaving another boundary.&lt;/p&gt;

&lt;p&gt;The secondary path needs its own:&lt;/p&gt;

&lt;p&gt;model configuration&lt;br&gt;
tool policy&lt;br&gt;
data policy&lt;br&gt;
timeout&lt;br&gt;
evaluation history&lt;/p&gt;

&lt;p&gt;Provider redundancy is useful only if the backup path is genuinely operational.&lt;/p&gt;

&lt;h2&gt;
  
  
  Degradation Level 2: remove free-form reasoning
&lt;/h2&gt;

&lt;p&gt;If general model generation is not trustworthy or available:&lt;/p&gt;

&lt;p&gt;Reasoning unavailable&lt;br&gt;
        ↓&lt;br&gt;
Bounded knowledge + authoritative tools&lt;/p&gt;

&lt;p&gt;The assistant may still be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;report business hours;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;provide a verified address;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;check an appointment;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;create a callback request;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;route a call;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;perform an explicitly authorized deterministic workflow.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It should stop pretending it can handle open-ended questions.&lt;/p&gt;

&lt;p&gt;A user message can be honest:&lt;/p&gt;

&lt;p&gt;"I'm having trouble with some of my answering capabilities right now, but I can still check your appointment or take a callback request."&lt;/p&gt;

&lt;p&gt;That is better than generating confident nonsense through a damaged reasoning path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Degradation Level 3: capture and route
&lt;/h2&gt;

&lt;p&gt;If even the bounded answer path is unavailable:&lt;/p&gt;

&lt;p&gt;Capture:&lt;br&gt;
name&lt;br&gt;
number&lt;br&gt;
reason&lt;br&gt;
urgency&lt;br&gt;
preferred callback time&lt;/p&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;p&gt;transfer&lt;br&gt;
     OR&lt;br&gt;
queue for callback&lt;br&gt;
     OR&lt;br&gt;
create service request&lt;/p&gt;

&lt;p&gt;This is not a glamorous AI experience.&lt;/p&gt;

&lt;p&gt;It can still preserve the business outcome.&lt;/p&gt;

&lt;p&gt;For many service businesses:&lt;/p&gt;

&lt;p&gt;accurately capture lead&lt;/p&gt;

&lt;p&gt;is preferable to:&lt;/p&gt;

&lt;p&gt;keep talking until something breaks&lt;/p&gt;

&lt;h2&gt;
  
  
  Degradation Level 4: fail safely
&lt;/h2&gt;

&lt;p&gt;Sometimes there is no trustworthy action remaining.&lt;/p&gt;

&lt;p&gt;Database unavailable.&lt;/p&gt;

&lt;p&gt;Tool authorization cannot be verified.&lt;/p&gt;

&lt;p&gt;Telephony state is inconsistent.&lt;/p&gt;

&lt;p&gt;Session integrity is uncertain.&lt;/p&gt;

&lt;p&gt;At that point:&lt;/p&gt;

&lt;p&gt;do not guess&lt;br&gt;
do not retry destructive tools&lt;br&gt;
do not fabricate confirmation&lt;/p&gt;

&lt;p&gt;Escalate, offer a safe alternate contact path, or terminate the automated interaction cleanly.&lt;/p&gt;

&lt;p&gt;A production system needs the ability to say:&lt;/p&gt;

&lt;p&gt;"I'm unable to complete that action right now."&lt;/p&gt;

&lt;p&gt;Failure disclosure is part of reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  The degradation ladder should be a state machine
&lt;/h2&gt;

&lt;p&gt;I would not scatter fallback behavior across dozens of exception handlers.&lt;/p&gt;

&lt;p&gt;Represent it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             NORMAL
                |
    primary path unavailable
                v
          DEGRADED_1
       secondary model
                |
          unavailable
                v
          DEGRADED_2
    bounded answers / tools
                |
          unavailable
                v
          DEGRADED_3
      capture + route
                |
          unsafe/unavailable
                v
          DEGRADED_4
          fail safely
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Persist the transitions:&lt;/p&gt;

&lt;p&gt;session_id&lt;br&gt;
from_mode&lt;br&gt;
to_mode&lt;br&gt;
reason&lt;br&gt;
dependency&lt;br&gt;
timestamp&lt;/p&gt;

&lt;p&gt;Now a reliability report can answer:&lt;/p&gt;

&lt;p&gt;How often do callers experience degraded mode?&lt;/p&gt;

&lt;p&gt;instead of simply:&lt;/p&gt;

&lt;p&gt;Uptime was 99.98%.&lt;/p&gt;

&lt;p&gt;Those are different measures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not every failure should trigger model failover
&lt;/h2&gt;

&lt;p&gt;This is another place where broad &lt;code&gt;except Exception&lt;/code&gt; logic becomes dangerous.&lt;/p&gt;

&lt;p&gt;If the primary LLM returns:&lt;/p&gt;

&lt;p&gt;401 Unauthorized&lt;/p&gt;

&lt;p&gt;do not automatically send the user's content to another provider.&lt;/p&gt;

&lt;p&gt;If the request was blocked because:&lt;/p&gt;

&lt;p&gt;tenant is suspended&lt;/p&gt;

&lt;p&gt;a model failover is wrong.&lt;/p&gt;

&lt;p&gt;If the tool failed because:&lt;/p&gt;

&lt;p&gt;authorization denied&lt;/p&gt;

&lt;p&gt;a retry against another tool is wrong.&lt;/p&gt;

&lt;p&gt;Fallback logic should understand failure classes.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;TIMEOUT&lt;br&gt;
    -&amp;gt; eligible for provider failover&lt;/p&gt;

&lt;p&gt;PROVIDER_5XX&lt;br&gt;
    -&amp;gt; perhaps eligible&lt;/p&gt;

&lt;p&gt;RATE_LIMIT&lt;br&gt;
    -&amp;gt; eligible if fallback approved&lt;/p&gt;

&lt;p&gt;AUTH_FAILURE&lt;br&gt;
    -&amp;gt; do not reroute&lt;/p&gt;

&lt;p&gt;POLICY_BLOCK&lt;br&gt;
    -&amp;gt; do not reroute&lt;/p&gt;

&lt;p&gt;INVALID_TOOL_REQUEST&lt;br&gt;
    -&amp;gt; repair or reject&lt;/p&gt;

&lt;p&gt;TENANT_NOT_FOUND&lt;br&gt;
    -&amp;gt; terminate&lt;/p&gt;

&lt;p&gt;DATA_BOUNDARY_VIOLATION&lt;br&gt;
    -&amp;gt; fail closed&lt;/p&gt;

&lt;p&gt;Graceful degradation should preserve policy.&lt;/p&gt;

&lt;p&gt;Not bypass it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The latency budget should be measured per stage
&lt;/h2&gt;

&lt;p&gt;One metric matters more than "LLM latency":&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long from the user's end of speech until useful response audio begins?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the conversational latency the user experiences.&lt;/p&gt;

&lt;p&gt;Break it down:&lt;/p&gt;

&lt;p&gt;User stops speaking&lt;br&gt;
       |&lt;br&gt;
       v&lt;br&gt;
Turn detection&lt;br&gt;
       |&lt;br&gt;
       v&lt;br&gt;
STT finalization&lt;br&gt;
       |&lt;br&gt;
       v&lt;br&gt;
Routing decision&lt;br&gt;
       |&lt;br&gt;
       v&lt;br&gt;
Knowledge/tool/model&lt;br&gt;
       |&lt;br&gt;
       v&lt;br&gt;
First response text&lt;br&gt;
       |&lt;br&gt;
       v&lt;br&gt;
TTS first audio&lt;br&gt;
       |&lt;br&gt;
       v&lt;br&gt;
User hears response&lt;/p&gt;

&lt;p&gt;Instrument each boundary.&lt;/p&gt;

&lt;p&gt;I would record at least:&lt;/p&gt;

&lt;p&gt;speech_end_at&lt;/p&gt;

&lt;p&gt;transcript_ready_at&lt;/p&gt;

&lt;p&gt;route_selected_at&lt;/p&gt;

&lt;p&gt;llm_requested_at&lt;/p&gt;

&lt;p&gt;llm_first_event_at&lt;/p&gt;

&lt;p&gt;tool_started_at&lt;/p&gt;

&lt;p&gt;tool_completed_at&lt;/p&gt;

&lt;p&gt;tts_requested_at&lt;/p&gt;

&lt;p&gt;tts_first_audio_at&lt;/p&gt;

&lt;p&gt;response_playback_at&lt;/p&gt;

&lt;p&gt;Now:&lt;/p&gt;

&lt;p&gt;"The model felt slow."&lt;/p&gt;

&lt;p&gt;can turn into:&lt;/p&gt;

&lt;p&gt;STT       180 ms&lt;br&gt;
routing    22 ms&lt;br&gt;
DB        310 ms&lt;br&gt;
LLM       890 ms&lt;br&gt;
tool     2300 ms&lt;br&gt;
TTS       190 ms&lt;/p&gt;

&lt;p&gt;The slowest thing may not be the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure P95 and P99, not just the demo
&lt;/h2&gt;

&lt;p&gt;A prototype tells you:&lt;/p&gt;

&lt;p&gt;this worked once&lt;/p&gt;

&lt;p&gt;Production needs to tell you:&lt;/p&gt;

&lt;p&gt;this usually works within the expected latency&lt;/p&gt;

&lt;p&gt;I would monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;end-of-speech to first response audio;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;model time to first event/token;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TTS time to first audio;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;tool latency;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;database query latency;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;event-loop lag;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;cold-start frequency and duration;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;routing distribution;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;fallback activation rate;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;abandoned generation rate;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WebSocket abnormal-close rate;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;transfer success;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;booking success;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;duplicate side-effect attempts.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For multi-tenant systems I would add one metric with a very different target:&lt;/p&gt;

&lt;p&gt;cross-tenant data exposure&lt;/p&gt;

&lt;p&gt;Target:&lt;/p&gt;

&lt;p&gt;zero&lt;/p&gt;

&lt;p&gt;A fast voice assistant with weak tenant isolation is not production-ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cold-start latency needs to be visible as its own metric
&lt;/h2&gt;

&lt;p&gt;Averages hide it.&lt;/p&gt;

&lt;p&gt;consider:&lt;/p&gt;

&lt;p&gt;95 calls:&lt;br&gt;
  700 ms response startup&lt;/p&gt;

&lt;p&gt;5 calls:&lt;br&gt;
  4.5 sec response startup&lt;/p&gt;

&lt;p&gt;Your average may still look respectable.&lt;/p&gt;

&lt;p&gt;Those five callers experience a different product.&lt;/p&gt;

&lt;p&gt;Log:&lt;/p&gt;

&lt;p&gt;container_warm: true / false&lt;/p&gt;

&lt;p&gt;or the closest runtime evidence you can derive, along with Modal task and region metadata.&lt;/p&gt;

&lt;p&gt;Modal exposes runtime information such as &lt;code&gt;MODAL_REGION&lt;/code&gt;, &lt;code&gt;MODAL_TASK_ID&lt;/code&gt;, image ID and cloud provider in the container environment. (&lt;a href="https://modal.com/docs/guide/environment_variables" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That gives you enough context to investigate whether a latency anomaly clusters around:&lt;/p&gt;

&lt;p&gt;cold container&lt;br&gt;
region&lt;br&gt;
image revision&lt;br&gt;
deployment&lt;/p&gt;

&lt;p&gt;instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep model initialization out of the first user turn
&lt;/h2&gt;

&lt;p&gt;Modal supports &lt;code&gt;@modal.enter&lt;/code&gt; lifecycle handlers for one-time initialization when a container starts. It specifically calls out model loading and expensive package initialization as uses. (&lt;a href="https://modal.com/docs/guide/lifecycle-functions" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;For an inference class:&lt;/p&gt;

&lt;p&gt;@app.cls(...)&lt;br&gt;
class ModelRuntime:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@modal.enter()
def load(self):
    self.model = load_model()

@modal.method()
async def generate(self, request):
    ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That still contributes to cold-start time when the container first appears.&lt;/p&gt;

&lt;p&gt;But it avoids accidentally reloading the same model for every user turn.&lt;/p&gt;

&lt;p&gt;For latency-critical workloads, combine:&lt;/p&gt;

&lt;p&gt;container initialization&lt;br&gt;
+&lt;br&gt;
warm capacity&lt;br&gt;
+&lt;br&gt;
startup profiling&lt;/p&gt;

&lt;p&gt;rather than optimizing only the inference call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the gateway from GPU inference when the workloads scale differently
&lt;/h2&gt;

&lt;p&gt;The FastAPI gateway and the model do not necessarily belong in the same Modal Function.&lt;/p&gt;

&lt;p&gt;They have different scaling characteristics.&lt;/p&gt;

&lt;p&gt;The gateway may need:&lt;/p&gt;

&lt;p&gt;many lightweight concurrent connections&lt;/p&gt;

&lt;p&gt;while a local model may need:&lt;/p&gt;

&lt;p&gt;few expensive GPU containers&lt;/p&gt;

&lt;p&gt;That suggests:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          FASTAPI / ASGI
      lightweight orchestration
              |
        async remote call
              |
              v
         GPU FUNCTION
      inference / reasoning
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Modal Functions scale independently, and its asynchronous API allows a coroutine to invoke remote functions without blocking the async caller. (&lt;a href="https://modal.com/docs/guide/async" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This separation is especially useful if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;you self-host a model;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GPU capacity is expensive;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;media connections outnumber simultaneous generations;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the inference layer needs a different concurrency policy.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not put a GPU behind every open socket if only a fraction of those sockets are actively generating.&lt;/p&gt;

&lt;h2&gt;
  
  
  The database and the gateway may need a different topology too
&lt;/h2&gt;

&lt;p&gt;A Modal container can be ephemeral.&lt;/p&gt;

&lt;p&gt;A Postgres connection is stateful.&lt;/p&gt;

&lt;p&gt;Those facts can collide.&lt;/p&gt;

&lt;p&gt;If each scale-up creates:&lt;/p&gt;

&lt;p&gt;50 containers&lt;br&gt;
x&lt;/p&gt;

&lt;h1&gt;
  
  
  20 DB connections
&lt;/h1&gt;

&lt;p&gt;1,000 connections&lt;/p&gt;

&lt;p&gt;the autoscaling tier has transferred its problem into Postgres.&lt;/p&gt;

&lt;p&gt;Use a bounded application pool and choose Supabase's direct/pooler mode deliberately.&lt;/p&gt;

&lt;p&gt;For bursty serverless-style connections, Supabase specifically recommends transaction pooling. For long-lived backend processes, direct or session-mode connections may make more sense depending on network constraints and connection budgets. (&lt;a href="https://supabase.com/docs/guides/database/connecting-to-postgres" rel="noopener noreferrer"&gt;supabase.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Autoscaling compute and fixed-capacity databases have to be designed together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supabase Realtime can help dashboards, but I would not put it in the audio path
&lt;/h2&gt;

&lt;p&gt;Suppose an operator dashboard needs to show:&lt;/p&gt;

&lt;p&gt;call connected&lt;/p&gt;

&lt;p&gt;AI speaking&lt;/p&gt;

&lt;p&gt;appointment booked&lt;/p&gt;

&lt;p&gt;transfer requested&lt;/p&gt;

&lt;p&gt;call ended&lt;/p&gt;

&lt;p&gt;Supabase Realtime can be useful there.&lt;/p&gt;

&lt;p&gt;Supabase currently recommends &lt;strong&gt;Broadcast&lt;/strong&gt; as the more scalable and secure approach for many realtime database-change scenarios, while Postgres Changes provides a simpler model with different scaling characteristics. (&lt;a href="https://supabase.com/docs/guides/realtime/subscribing-to-database-changes" rel="noopener noreferrer"&gt;supabase.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That is a good control-plane use case.&lt;/p&gt;

&lt;p&gt;I would not turn this into:&lt;/p&gt;

&lt;p&gt;audio&lt;br&gt;
 ↓&lt;br&gt;
Postgres&lt;br&gt;
 ↓&lt;br&gt;
Realtime&lt;br&gt;
 ↓&lt;br&gt;
voice runtime&lt;/p&gt;

&lt;p&gt;The operator's dashboard and the caller's audio stream have very different latency requirements.&lt;/p&gt;

&lt;p&gt;Do not force them through the same path simply because both are "real-time."&lt;/p&gt;

&lt;h2&gt;
  
  
  Voice session state should have an owner
&lt;/h2&gt;

&lt;p&gt;A call may have:&lt;/p&gt;

&lt;p&gt;tenant&lt;br&gt;
agent version&lt;br&gt;
caller&lt;br&gt;
conversation history&lt;br&gt;
current speaker&lt;br&gt;
active generation&lt;br&gt;
current tool&lt;br&gt;
pending confirmation&lt;br&gt;
degradation level&lt;br&gt;
transfer state&lt;br&gt;
deadline&lt;/p&gt;

&lt;p&gt;Where does that state live?&lt;/p&gt;

&lt;p&gt;If the answer is:&lt;/p&gt;

&lt;p&gt;some of it in Redis&lt;br&gt;
some in Postgres&lt;br&gt;
some in a Modal global&lt;br&gt;
some in the WebSocket handler&lt;br&gt;
some in the LLM messages&lt;/p&gt;

&lt;p&gt;you will eventually get state disagreements.&lt;/p&gt;

&lt;p&gt;For each state field, define:&lt;/p&gt;

&lt;p&gt;authoritative owner&lt;br&gt;
durability&lt;br&gt;
lifetime&lt;br&gt;
recovery behavior&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;State&lt;br&gt;
Owner&lt;br&gt;
Durability&lt;/p&gt;

&lt;p&gt;Active audio buffer&lt;br&gt;
session runtime&lt;br&gt;
ephemeral&lt;/p&gt;

&lt;p&gt;Current speaking state&lt;br&gt;
session runtime&lt;br&gt;
ephemeral&lt;/p&gt;

&lt;p&gt;Agent configuration version&lt;br&gt;
Postgres to snapshot&lt;br&gt;
durable&lt;/p&gt;

&lt;p&gt;Booking&lt;br&gt;
scheduling system&lt;br&gt;
authoritative external&lt;/p&gt;

&lt;p&gt;Tool attempt&lt;br&gt;
Postgres ledger&lt;br&gt;
durable&lt;/p&gt;

&lt;p&gt;Transcript&lt;br&gt;
session + durable sink&lt;br&gt;
eventually durable&lt;/p&gt;

&lt;p&gt;Degradation mode&lt;br&gt;
runtime + event log&lt;br&gt;
recoverable&lt;/p&gt;

&lt;p&gt;Call outcome&lt;br&gt;
Postgres&lt;br&gt;
durable&lt;/p&gt;

&lt;p&gt;This is not glamorous architecture work.&lt;/p&gt;

&lt;p&gt;It prevents extraordinarily confusing failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Barge-in is a state transition, not simply another prompt
&lt;/h2&gt;

&lt;p&gt;A live voice user will interrupt.&lt;/p&gt;

&lt;p&gt;If the system is speaking and new speech begins, the orchestration path may need to:&lt;/p&gt;

&lt;p&gt;detect new user turn&lt;/p&gt;

&lt;p&gt;cancel/stop TTS playback&lt;/p&gt;

&lt;p&gt;possibly cancel generation&lt;/p&gt;

&lt;p&gt;invalidate queued audio&lt;/p&gt;

&lt;p&gt;preserve relevant context&lt;/p&gt;

&lt;p&gt;begin listening&lt;/p&gt;

&lt;p&gt;resume reasoning&lt;/p&gt;

&lt;p&gt;If you treat the interrupt as:&lt;/p&gt;

&lt;p&gt;append new text to chat history&lt;/p&gt;

&lt;p&gt;you can end up playing stale audio after the user has already changed the conversation.&lt;/p&gt;

&lt;p&gt;That is why I think of real-time voice more as a distributed state machine than a chatbot with a microphone attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cancellation needs to cross every layer
&lt;/h2&gt;

&lt;p&gt;Suppose the user interrupts while an expensive reasoning request is running.&lt;/p&gt;

&lt;p&gt;You stop TTS.&lt;/p&gt;

&lt;p&gt;Good.&lt;/p&gt;

&lt;p&gt;But what happens to:&lt;/p&gt;

&lt;p&gt;LLM generation?&lt;/p&gt;

&lt;p&gt;tool execution?&lt;/p&gt;

&lt;p&gt;database operation?&lt;/p&gt;

&lt;p&gt;queued audio?&lt;/p&gt;

&lt;p&gt;background task?&lt;/p&gt;

&lt;p&gt;If they all continue, the user experiences one conversation while the backend continues another.&lt;/p&gt;

&lt;p&gt;The correct cancellation boundary depends on whether the action is reversible.&lt;/p&gt;

&lt;p&gt;For generation:&lt;/p&gt;

&lt;p&gt;cancel if supported&lt;/p&gt;

&lt;p&gt;For a tool that may have already committed:&lt;/p&gt;

&lt;p&gt;do not assume cancellation means rollback&lt;/p&gt;

&lt;p&gt;That is exactly why tool execution needs idempotency and durable state.&lt;/p&gt;

&lt;h2&gt;
  
  
  The voice stack should have two clocks
&lt;/h2&gt;

&lt;p&gt;I like separating:&lt;/p&gt;

&lt;p&gt;TURN DEADLINE&lt;/p&gt;

&lt;p&gt;from:&lt;/p&gt;

&lt;p&gt;SESSION LIFETIME&lt;/p&gt;

&lt;p&gt;A call may last 20 minutes.&lt;/p&gt;

&lt;p&gt;One reasoning turn should not automatically be permitted to consume those 20 minutes.&lt;/p&gt;

&lt;p&gt;The turn has:&lt;/p&gt;

&lt;p&gt;STT budget&lt;br&gt;
routing budget&lt;br&gt;
reasoning budget&lt;br&gt;
tool budget&lt;br&gt;
TTS budget&lt;/p&gt;

&lt;p&gt;The session has:&lt;/p&gt;

&lt;p&gt;overall call lifetime&lt;br&gt;
idle policy&lt;br&gt;
authentication lifetime&lt;br&gt;
resource limits&lt;/p&gt;

&lt;p&gt;If those clocks are not distinct, a stuck turn can effectively own the entire connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Avoid one global timeout
&lt;/h2&gt;

&lt;p&gt;This:&lt;/p&gt;

&lt;p&gt;TIMEOUT = 30&lt;/p&gt;

&lt;p&gt;is attractive.&lt;/p&gt;

&lt;p&gt;It is usually ambiguous.&lt;/p&gt;

&lt;p&gt;Thirty seconds for what?&lt;/p&gt;

&lt;p&gt;database query?&lt;/p&gt;

&lt;p&gt;LLM generation?&lt;/p&gt;

&lt;p&gt;tool call?&lt;/p&gt;

&lt;p&gt;session idle?&lt;/p&gt;

&lt;p&gt;WebSocket heartbeat?&lt;/p&gt;

&lt;p&gt;HTTP request?&lt;/p&gt;

&lt;p&gt;Modal Function?&lt;/p&gt;

&lt;p&gt;Each timeout should answer a specific operational question.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;MODEL_FIRST_EVENT_DEADLINE&lt;/p&gt;

&lt;p&gt;TOOL_EXECUTION_DEADLINE&lt;/p&gt;

&lt;p&gt;DATABASE_QUERY_DEADLINE&lt;/p&gt;

&lt;p&gt;SESSION_IDLE_DEADLINE&lt;/p&gt;

&lt;p&gt;CALL_MAX_DURATION&lt;/p&gt;

&lt;p&gt;Once a timeout has a name, its fallback behavior becomes easier to design.&lt;/p&gt;

&lt;h2&gt;
  
  
  A production routing envelope
&lt;/h2&gt;

&lt;p&gt;I would want every turn to carry something close to:&lt;/p&gt;

&lt;p&gt;@dataclass&lt;br&gt;
class TurnContext:&lt;br&gt;
    organization_id: str&lt;br&gt;
    session_id: str&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agent_version_id: str

allowed_tools: set[str]

degradation_mode: str

turn_deadline: float

request_id: str

trace_id: str
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;p&gt;reasoning&lt;br&gt;
retrieval&lt;br&gt;
tool calls&lt;br&gt;
persistence&lt;/p&gt;

&lt;p&gt;all receive the same context.&lt;/p&gt;

&lt;p&gt;That avoids rediscovering tenant identity from untrusted parameters deep inside the call path.&lt;/p&gt;

&lt;p&gt;It also makes logs traceable.&lt;/p&gt;

&lt;h2&gt;
  
  
  One trace ID should cross the whole call
&lt;/h2&gt;

&lt;p&gt;A voice incident becomes painful when every service uses its own identifier.&lt;/p&gt;

&lt;p&gt;I would propagate:&lt;/p&gt;

&lt;p&gt;organization_id&lt;br&gt;
call_session_id&lt;br&gt;
turn_id&lt;br&gt;
trace_id&lt;/p&gt;

&lt;p&gt;across:&lt;/p&gt;

&lt;p&gt;telephony/media provider&lt;/p&gt;

&lt;p&gt;FastAPI&lt;/p&gt;

&lt;p&gt;Modal Function&lt;/p&gt;

&lt;p&gt;STT&lt;/p&gt;

&lt;p&gt;LLM&lt;/p&gt;

&lt;p&gt;tool execution&lt;/p&gt;

&lt;p&gt;TTS&lt;/p&gt;

&lt;p&gt;Postgres&lt;/p&gt;

&lt;p&gt;A useful event:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "event": "reasoning_fallback",&lt;br&gt;
  "organization_id": "org_...",&lt;br&gt;
  "session_id": "call_...",&lt;br&gt;
  "turn_id": "turn_...",&lt;br&gt;
  "trace_id": "trace_...",&lt;br&gt;
  "from": "primary_llm",&lt;br&gt;
  "to": "bounded_fast_path",&lt;br&gt;
  "reason": "first_event_deadline_exceeded"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Now an incident can be reconstructed.&lt;/p&gt;

&lt;p&gt;Without correlation, you are reading five log systems and hoping the timestamps line up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not log everything simply because voice is hard to debug
&lt;/h2&gt;

&lt;p&gt;A voice system handles unusually sensitive data.&lt;/p&gt;

&lt;p&gt;Potentially:&lt;/p&gt;

&lt;p&gt;phone numbers&lt;/p&gt;

&lt;p&gt;names&lt;/p&gt;

&lt;p&gt;addresses&lt;/p&gt;

&lt;p&gt;appointment details&lt;/p&gt;

&lt;p&gt;health information&lt;/p&gt;

&lt;p&gt;financial details&lt;/p&gt;

&lt;p&gt;raw audio&lt;/p&gt;

&lt;p&gt;transcripts&lt;/p&gt;

&lt;p&gt;credentials spoken accidentally&lt;/p&gt;

&lt;p&gt;Observability is important.&lt;/p&gt;

&lt;p&gt;Unlimited logging is not.&lt;/p&gt;

&lt;p&gt;Separate:&lt;/p&gt;

&lt;p&gt;operational metadata&lt;/p&gt;

&lt;p&gt;from:&lt;/p&gt;

&lt;p&gt;conversation content&lt;/p&gt;

&lt;p&gt;and define retention independently.&lt;/p&gt;

&lt;p&gt;Do not put full transcripts into every exception log simply because it is convenient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modal Secrets belong at the runtime boundary
&lt;/h2&gt;

&lt;p&gt;Modal provides &lt;code&gt;Secret&lt;/code&gt; objects specifically for injecting credentials into Functions as environment variables rather than hard-coding them into images or source. (&lt;a href="https://modal.com/docs/guide/secrets" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;@app.function(&lt;br&gt;
    secrets=[&lt;br&gt;
        modal.Secret.from_name('voice-production'),&lt;br&gt;
    ],&lt;br&gt;
)&lt;br&gt;
def runtime():&lt;br&gt;
    ...&lt;/p&gt;

&lt;p&gt;I would still keep secrets scoped.&lt;/p&gt;

&lt;p&gt;One enormous secret bundle containing:&lt;/p&gt;

&lt;p&gt;database admin&lt;br&gt;
telephony&lt;br&gt;
LLM provider&lt;br&gt;
calendar&lt;br&gt;
CRM&lt;br&gt;
payments&lt;/p&gt;

&lt;p&gt;means every function receiving it has all of those capabilities.&lt;/p&gt;

&lt;p&gt;Prefer the smallest credentials required by each execution surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dev and production should not share one mutable runtime
&lt;/h2&gt;

&lt;p&gt;Modal Environments isolate applications and resources such as Secrets between environments, making them suitable for dev/prod separation. (&lt;a href="https://modal.com/docs/guide/environments" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That matters for voice.&lt;/p&gt;

&lt;p&gt;A developer testing:&lt;/p&gt;

&lt;p&gt;transfer()&lt;/p&gt;

&lt;p&gt;should not accidentally trigger the production transfer target because both deployments shared the same secrets and state.&lt;/p&gt;

&lt;p&gt;Environment boundaries should include:&lt;/p&gt;

&lt;p&gt;Modal app&lt;/p&gt;

&lt;p&gt;Secrets&lt;/p&gt;

&lt;p&gt;database project/schema&lt;/p&gt;

&lt;p&gt;telephony configuration&lt;/p&gt;

&lt;p&gt;model/provider keys&lt;/p&gt;

&lt;p&gt;tool endpoints&lt;/p&gt;

&lt;p&gt;where practical.&lt;/p&gt;

&lt;p&gt;"Environment" should describe an actual isolation boundary.&lt;/p&gt;

&lt;p&gt;Not a string in a log message.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture should fail locally before it fails conversationally
&lt;/h2&gt;

&lt;p&gt;Here is the principle I keep returning to.&lt;/p&gt;

&lt;p&gt;A production voice agent has enough uncertainty already.&lt;/p&gt;

&lt;p&gt;The control plane should remove uncertainty where it can.&lt;/p&gt;

&lt;p&gt;Before a call begins, validate:&lt;/p&gt;

&lt;p&gt;tenant exists&lt;/p&gt;

&lt;p&gt;agent version exists&lt;/p&gt;

&lt;p&gt;channel belongs to tenant&lt;/p&gt;

&lt;p&gt;configuration is valid&lt;/p&gt;

&lt;p&gt;required credentials exist&lt;/p&gt;

&lt;p&gt;tool policy is loadable&lt;/p&gt;

&lt;p&gt;fallback policy exists&lt;/p&gt;

&lt;p&gt;Then admit the session.&lt;/p&gt;

&lt;p&gt;Do not wait until minute four of a customer conversation to discover:&lt;/p&gt;

&lt;p&gt;there is no calendar credential&lt;/p&gt;

&lt;p&gt;if the system could have detected that before answering the call.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four things I would benchmark before calling the stack production-ready
&lt;/h2&gt;

&lt;p&gt;Not "Does the demo work?"&lt;/p&gt;

&lt;p&gt;These four.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normal latency
&lt;/h2&gt;

&lt;p&gt;Under expected traffic:&lt;/p&gt;

&lt;p&gt;How long does a normal conversational turn take?&lt;/p&gt;

&lt;p&gt;Break it down by stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Burst behavior
&lt;/h2&gt;

&lt;p&gt;When ten calls become fifty:&lt;/p&gt;

&lt;p&gt;Does latency degrade gradually,&lt;br&gt;
or does the system fall off a cliff?&lt;/p&gt;

&lt;p&gt;Watch Modal queueing, cold starts, external APIs and database connections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dependency failure
&lt;/h2&gt;

&lt;p&gt;Deliberately remove:&lt;/p&gt;

&lt;p&gt;primary LLM&lt;br&gt;
database&lt;br&gt;
booking API&lt;br&gt;
TTS&lt;/p&gt;

&lt;p&gt;one at a time.&lt;/p&gt;

&lt;p&gt;Does the system enter the intended degraded state?&lt;/p&gt;

&lt;h2&gt;
  
  
  Tenant isolation
&lt;/h2&gt;

&lt;p&gt;Attempt:&lt;/p&gt;

&lt;p&gt;tenant A session&lt;br&gt;
     ↓&lt;br&gt;
tenant B identifier&lt;/p&gt;

&lt;p&gt;across every privileged route.&lt;/p&gt;

&lt;p&gt;The expected result is not:&lt;/p&gt;

&lt;p&gt;usually blocked&lt;/p&gt;

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

&lt;p&gt;blocked&lt;/p&gt;

&lt;p&gt;every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  A CTO should care about degradation rate as much as uptime
&lt;/h2&gt;

&lt;p&gt;consider two systems.&lt;/p&gt;

&lt;p&gt;System A:&lt;/p&gt;

&lt;p&gt;99.99% process uptime&lt;/p&gt;

&lt;p&gt;but:&lt;/p&gt;

&lt;p&gt;8% of calls spend &amp;gt;10 seconds waiting for response&lt;/p&gt;

&lt;p&gt;4% silently enter broken booking path&lt;/p&gt;

&lt;p&gt;System B:&lt;/p&gt;

&lt;p&gt;99.9% process uptime&lt;/p&gt;

&lt;p&gt;but:&lt;/p&gt;

&lt;p&gt;dependency failures trigger a clear fallback&lt;br&gt;
calls continue in capture-and-route mode&lt;br&gt;
no false booking confirmations&lt;/p&gt;

&lt;p&gt;Which system is more reliable?&lt;/p&gt;

&lt;p&gt;Infrastructure uptime alone cannot answer.&lt;/p&gt;

&lt;p&gt;For real-time AI I would put these on the executive dashboard:&lt;/p&gt;

&lt;p&gt;successful conversation rate&lt;/p&gt;

&lt;p&gt;P95 turn latency&lt;/p&gt;

&lt;p&gt;P99 turn latency&lt;/p&gt;

&lt;p&gt;fallback activation rate&lt;/p&gt;

&lt;p&gt;fallback recovery rate&lt;/p&gt;

&lt;p&gt;tool success rate&lt;/p&gt;

&lt;p&gt;false confirmation incidents&lt;/p&gt;

&lt;p&gt;transfer success&lt;/p&gt;

&lt;p&gt;abandoned calls during AI wait&lt;/p&gt;

&lt;p&gt;cold-start exposure&lt;/p&gt;

&lt;p&gt;tenant-isolation failures&lt;/p&gt;

&lt;p&gt;That is closer to the actual product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the standard "LLM wrapper" architecture fails
&lt;/h2&gt;

&lt;p&gt;A wrapper tutorial usually assumes:&lt;/p&gt;

&lt;p&gt;input&lt;br&gt;
 ↓&lt;br&gt;
model&lt;br&gt;
 ↓&lt;br&gt;
output&lt;/p&gt;

&lt;p&gt;A production voice system actually behaves more like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 SESSION STATE
                      |
                      v
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;AUDIO -&amp;gt; STT -&amp;gt; ROUTER -&amp;gt; POLICY -&amp;gt; REASONING&lt;br&gt;
                 |         |          |&lt;br&gt;
                 |         |          +-&amp;gt; LLM&lt;br&gt;
                 |         |&lt;br&gt;
                 |         +-&amp;gt; TOOLS&lt;br&gt;
                 |&lt;br&gt;
                 +-&amp;gt; FAST PATH&lt;br&gt;
                 |&lt;br&gt;
                 +-&amp;gt; CONTROL EVENTS&lt;br&gt;
                          |&lt;br&gt;
                          v&lt;br&gt;
                         TTS&lt;br&gt;
                          |&lt;br&gt;
                          v&lt;br&gt;
                        AUDIO&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;         ^                    ^

   TENANT CONTROL         DURABLE EVIDENCE
     Postgres                Postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The LLM is one component.&lt;/p&gt;

&lt;p&gt;That is the architectural shift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Kestrel Voice changed my view of this
&lt;/h2&gt;

&lt;p&gt;Working on Kestrel Voice made this distinction much more concrete for me.&lt;/p&gt;

&lt;p&gt;The public product spans AI phone answering, appointment booking, RAG-powered answers, transcripts, call intelligence and integrations with external business systems. (&lt;a href="https://www.kestrelvoice.com/" rel="noopener noreferrer"&gt;kestrelvoice.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Once a voice system crosses that many boundaries, "the model answered correctly" is only one success condition.&lt;/p&gt;

&lt;p&gt;The interaction can still fail because:&lt;/p&gt;

&lt;p&gt;the booking tool did not confirm&lt;/p&gt;

&lt;p&gt;the media connection disappeared&lt;/p&gt;

&lt;p&gt;the wrong tenant configuration loaded&lt;/p&gt;

&lt;p&gt;the fallback repeated a side effect&lt;/p&gt;

&lt;p&gt;the caller interrupted stale TTS&lt;/p&gt;

&lt;p&gt;the database could not persist the outcome&lt;/p&gt;

&lt;p&gt;the secondary provider violated a policy boundary&lt;/p&gt;

&lt;p&gt;That changed the way I think about voice architecture.&lt;/p&gt;

&lt;p&gt;The most important component is not always the model.&lt;/p&gt;

&lt;p&gt;It is often the orchestration layer deciding &lt;strong&gt;what is allowed to happen next&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a deeper look at the product that shaped this thinking, see &lt;a href="https://subodhkc.com/blog/ai-voice-agent-architecture-kestrelvoice" rel="noopener noreferrer"&gt;the KestrelVoice architecture detailed look&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real three-tier performance strategy
&lt;/h2&gt;

&lt;p&gt;If I had to reduce the entire architecture to one diagram, it would be this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                INCOMING TURN
                     |
                     v
          +---------------------+
          | CAN APP LOGIC      |
          | HANDLE IT SAFELY?  |
          +---------+----------+
                    |
             yes    |    no
             v      |     v
      CONTROL ACTION|  BOUNDED FAST PATH?
                    |        |
                    |   yes  |  no
                    |    v   |   v
                    |  FAST  | GENERAL
                    | ANSWER | REASONING
                    |        |
                    +----+---+
                         |
                         v
                     VERIFIED
                      ACTION
                         |
                         v
                        TTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The performance insight is not:&lt;/p&gt;

&lt;p&gt;use a smaller model.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;do not invoke general reasoning when the problem has a safer, faster bounded path.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Can Modal host a FastAPI voice agent?
&lt;/h2&gt;

&lt;p&gt;Yes. Modal can serve ASGI applications through &lt;code&gt;@modal.asgi_app()&lt;/code&gt;, including FastAPI, and Modal's ASGI-hosted Web Functions support WebSockets. Modal also supports input concurrency so a container can handle multiple simultaneous WebSocket connections. (&lt;a href="https://modal.com/docs/guide/webhooks" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Does Modal support WebSockets?
&lt;/h2&gt;

&lt;p&gt;Yes. Modal currently supports the WebSocket protocol for &lt;code&gt;@modal.asgi_app&lt;/code&gt;, &lt;code&gt;@modal.wsgi_app&lt;/code&gt; and &lt;code&gt;@modal.web_server&lt;/code&gt; functions. Modal documents each WebSocket as one Function call per connection. It currently limits individual WebSocket messages to 2 MiB and does not support RFC 8441 WebSockets over HTTP/2 or the &lt;code&gt;permessage-deflate&lt;/code&gt; extension. (&lt;a href="https://modal.com/docs/guide/webhooks" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Should I use &lt;code&gt;modal.fastapi_endpoint&lt;/code&gt; for a voice WebSocket?
&lt;/h2&gt;

&lt;p&gt;For an application with actual WebSocket routes, use a FastAPI ASGI application exposed through &lt;code&gt;@modal.asgi_app()&lt;/code&gt; rather than treating the connection like an ordinary &lt;code&gt;fastapi_endpoint&lt;/code&gt;. Modal explicitly documents WebSocket support for its ASGI/web-server hosting modes. (&lt;a href="https://modal.com/docs/guide/webhooks" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I reduce Modal cold-start latency for a voice agent?
&lt;/h2&gt;

&lt;p&gt;Measure first, then consider &lt;code&gt;min_containers&lt;/code&gt;, &lt;code&gt;buffer_containers&lt;/code&gt;, a longer &lt;code&gt;scaledown_window&lt;/code&gt;, smaller initialization work and lifecycle initialization through &lt;code&gt;@modal.enter&lt;/code&gt;. Modal documents these as controls for reducing queueing and container-initialization latency. They trade cost against latency. (&lt;a href="https://modal.com/docs/guide/cold-start" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Modal's default Function timeout?
&lt;/h2&gt;

&lt;p&gt;Current Modal Functions have a default execution timeout of 300 seconds. Modal allows configured timeouts from one second to 24 hours. Since Modal documents each WebSocket connection as one Function call, long-lived voice or browser sessions should use an explicitly chosen Function timeout appropriate to the session policy. (&lt;a href="https://modal.com/docs/guide/timeouts" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Should Postgres store live voice audio frames?
&lt;/h2&gt;

&lt;p&gt;Usually I would keep raw real-time media out of the transactional Postgres hot path. Postgres is better suited to durable session, configuration, transcript, tool and outcome records. Raw recordings or media are generally better handled through media/object storage designed for that payload, while Postgres stores references and metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use Supabase/Postgres in an AI voice platform?
&lt;/h2&gt;

&lt;p&gt;Postgres is useful as the durable control plane: organizations, users, voice-agent versions, sessions, tools, bookings, transcripts and audit metadata. Supabase adds Auth, Realtime and managed Postgres capabilities on top of that foundation. (&lt;a href="https://supabase.com/docs/guides/database/overview" rel="noopener noreferrer"&gt;supabase.com&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  How should I handle multiple tenants in a voice-agent database?
&lt;/h2&gt;

&lt;p&gt;Resolve tenant context from trusted authentication or channel information, include the organization identifier on tenant-owned data and enforce authorization at every access layer. With Supabase, RLS can provide row-level controls for user-facing access. Be aware that trusted service-role credentials can bypass RLS, so privileged backend paths require their own explicit authorization controls. (&lt;a href="https://supabase.com/docs/guides/database/postgres/row-level-security" rel="noopener noreferrer"&gt;supabase.com&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Should every voice-agent query go through Postgres RLS?
&lt;/h2&gt;

&lt;p&gt;That depends on the connection model. RLS is highly useful for user-scoped Supabase access. A trusted backend using credentials that bypass RLS must enforce tenant isolation itself or use a more constrained database role/access pattern. Do not assume RLS protects a query that is deliberately running with bypass privileges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should my AI voice agent send every question to the LLM?
&lt;/h2&gt;

&lt;p&gt;No. Session-control events should be handled deterministically, and bounded questions can often use authoritative data or a constrained fast path. General LLM reasoning is most useful for turns that genuinely require synthesis, ambiguity resolution or planning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a fast path in an AI voice architecture?
&lt;/h2&gt;

&lt;p&gt;A fast path is a bounded response route that avoids full general reasoning when the answer can be obtained from an approved source or deterministic workflow. Examples include business hours, location information, appointment lookup or structured routing. The route may still use probabilistic language classification, so the confidence and risk of the action should determine when it is safe to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should happen if the LLM provider goes down?
&lt;/h2&gt;

&lt;p&gt;Design this before production. A useful degradation sequence is: approved secondary model/provider, bounded deterministic or retrieval/tool capabilities, capture-and-route mode, and finally safe escalation or termination when no trustworthy operation remains.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should I automatically retry failed booking tools?
&lt;/h2&gt;

&lt;p&gt;Not blindly. A booking may have succeeded even if the acknowledgement failed. Side-effecting operations should use idempotency keys and a durable execution ledger so retries can determine whether the logical operation already occurred.&lt;/p&gt;

&lt;h2&gt;
  
  
  How should I measure AI voice latency?
&lt;/h2&gt;

&lt;p&gt;Measure the user-facing turn from end of speech to first useful response audio, then decompose it into speech recognition, routing, retrieval/database, LLM time to first event, tool execution and TTS time to first audio. Track P95 and P99 as well as averages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does Supabase Realtime belong in the live audio path?
&lt;/h2&gt;

&lt;p&gt;I would generally use it for control-plane experiences such as dashboards, call-state notifications or database-driven updates rather than as the audio transport itself. Supabase currently recommends Broadcast for scalable database-change notification use cases, while Postgres Changes is simpler but has different scaling characteristics. (&lt;a href="https://supabase.com/docs/guides/realtime/subscribing-to-database-changes" rel="noopener noreferrer"&gt;supabase.com&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Should Modal and Supabase run in the same region?
&lt;/h2&gt;

&lt;p&gt;For latency-sensitive applications, keeping compute near users and data can remove avoidable network delay. Modal supports region selection and beta regional request routing, and Supabase offers multiple database deployment regions. Benchmark the actual network path rather than assuming geography is negligible. (&lt;a href="https://modal.com/docs/guide/region-selection" rel="noopener noreferrer"&gt;modal.com&lt;/a&gt;) (&lt;a href="https://supabase.com/docs/guides/platform/regions" rel="noopener noreferrer"&gt;supabase.com&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Checklist: Modal + FastAPI + Postgres AI Voice
&lt;/h2&gt;

&lt;p&gt;Before calling the architecture production-ready:&lt;/p&gt;

&lt;p&gt;[ ] Separate media, orchestration and persistence paths&lt;br&gt;
[ ] Host WebSocket routes through an ASGI application&lt;br&gt;
[ ] Keep blocking work off the FastAPI event loop&lt;br&gt;
[ ] Measure Modal cold-start latency&lt;br&gt;
[ ] Configure warm capacity from traffic data&lt;br&gt;
[ ] Configure WebSocket Function timeout deliberately&lt;br&gt;
[ ] Benchmark per-container WebSocket concurrency&lt;br&gt;
[ ] Align compute, database and model geography&lt;br&gt;
[ ] Bound Postgres connection pools&lt;br&gt;
[ ] Resolve tenant before loading session configuration&lt;br&gt;
[ ] Test tenant isolation across every privileged endpoint&lt;br&gt;
[ ] Understand which backend credentials bypass RLS&lt;br&gt;
[ ] Snapshot the agent configuration version at session start&lt;br&gt;
[ ] Keep high-frequency audio/token state out of Postgres hot loops&lt;br&gt;
[ ] Handle session controls before general reasoning&lt;br&gt;
[ ] Create a bounded fast path for authoritative answers&lt;br&gt;
[ ] Reserve general LLM reasoning for turns that need it&lt;br&gt;
[ ] Verify transactional actions before confirming them to callers&lt;br&gt;
[ ] Make side-effecting tools idempotent&lt;br&gt;
[ ] Give each turn an explicit deadline&lt;br&gt;
[ ] Give the session a separate lifetime&lt;br&gt;
[ ] Implement an approved provider failover path&lt;br&gt;
[ ] Implement bounded degraded operation&lt;br&gt;
[ ] Implement capture-and-route fallback&lt;br&gt;
[ ] Implement a fail-safe state&lt;br&gt;
[ ] Persist degradation transitions and reasons&lt;br&gt;
[ ] Propagate one trace ID across the full interaction&lt;br&gt;
[ ] Measure end-of-speech to first response audio&lt;br&gt;
[ ] Measure P95/P99, not just averages&lt;br&gt;
[ ] Load-test concurrent calls&lt;br&gt;
[ ] Chaos-test LLM, DB, tool and TTS failures&lt;br&gt;
[ ] Verify cancellation and barge-in behavior&lt;/p&gt;

&lt;p&gt;A low-latency voice system is not created by finding the fastest model and putting a WebSocket in front of it.&lt;/p&gt;

&lt;p&gt;It comes from removing unnecessary work from the critical path.&lt;/p&gt;

&lt;p&gt;Keep session state close to the live connection.&lt;/p&gt;

&lt;p&gt;Keep durable truth in Postgres.&lt;/p&gt;

&lt;p&gt;Keep tenant boundaries explicit.&lt;/p&gt;

&lt;p&gt;Handle deterministic controls before reasoning.&lt;/p&gt;

&lt;p&gt;Use the fast path when the answer is bounded.&lt;/p&gt;

&lt;p&gt;Use the LLM when reasoning is genuinely required.&lt;/p&gt;

&lt;p&gt;Verify side effects before telling a caller they happened.&lt;/p&gt;

&lt;p&gt;And when the intelligence layer fails, degrade into something smaller and trustworthy rather than something equally conversational but less reliable.&lt;/p&gt;

&lt;p&gt;That is the part most voice-agent demos do not show.&lt;/p&gt;

&lt;p&gt;It is also the part that decides whether the architecture still works after the demo traffic becomes real traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continue Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://subodhkc.com/blog/ai-voice-agent-architecture-kestrelvoice" rel="noopener noreferrer"&gt;How AI Voice Agents Work: KestrelVoice Architecture detailed look&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://subodhkc.com/blog/fastapi-websocket-1006-llm-streaming-disconnect" rel="noopener noreferrer"&gt;Fix FastAPI WebSocket 1006 During LLM Streaming&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://subodhkc.com/blog/implementing-rag-row-level-security-for-multi-tenant-ai" rel="noopener noreferrer"&gt;Implementing RAG Row-Level Security for Multi-Tenant AI&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://subodhkc.com/blog/12-production-readiness-checks-for-ai-pilots" rel="noopener noreferrer"&gt;12 Production Readiness Checks for AI Pilots&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://subodhkc.com/blog/recover-a-stalled-ai-pilot-in-30-days" rel="noopener noreferrer"&gt;Recover a Stalled AI Pilot in 30 Days&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://subodhkc.com/blog/modal-fastapi-postgres-ai-voice-pipeline-architecture" rel="noopener noreferrer"&gt;subodhkc.com&lt;/a&gt;. Explore &lt;a href="https://kestrelvoice.com" rel="noopener noreferrer"&gt;KestrelVoice AI Voice Operations&lt;/a&gt;. Follow for more on AI governance, enterprise architecture, and compliance engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>modalfastapivoiceage</category>
      <category>realtimeaireasoningp</category>
      <category>aivoiceplatformarchi</category>
      <category>lowlatencyaivoiceage</category>
    </item>
    <item>
      <title>Texas HB 149 vs the EU AI Act: What Engineers Must Build</title>
      <dc:creator>Subodh Kc</dc:creator>
      <pubDate>Thu, 01 Oct 2026 15:18:33 +0000</pubDate>
      <link>https://dev.to/subodhkc/texas-hb-149-vs-the-eu-ai-act-what-engineers-must-build-26pb</link>
      <guid>https://dev.to/subodhkc/texas-hb-149-vs-the-eu-ai-act-what-engineers-must-build-26pb</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is an engineering and governance analysis, not legal advice. Legal applicability and interpretation should be reviewed with qualified counsel.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Engineering Problem Underneath Two Different Laws
&lt;/h2&gt;

&lt;p&gt;Production AI does not stand still after a compliance checklist is completed. The model changes. A vendor replaces an underlying model. Retrieval data changes. A prompt changes. An engineer adds another tool. Someone increases an autonomy setting. A team begins using the same system for a use case that was never part of the original review.&lt;/p&gt;

&lt;p&gt;The application that legal approved six months ago may no longer be the application running today.&lt;/p&gt;

&lt;p&gt;That is why engineering leaders should approach laws such as the &lt;strong&gt;Texas Responsible Artificial Intelligence Governance Act, or TRAIGA, enacted through Texas HB 149&lt;/strong&gt;, and the &lt;strong&gt;EU AI Act&lt;/strong&gt; as specifications that eventually have to meet a running system. Not as two collections of forms.&lt;/p&gt;

&lt;p&gt;Both laws, despite their structural differences, ultimately require the same engineering capabilities: know what AI you deployed, why it is allowed to operate, what changed, what evidence exists, and whether the system is still behaving inside its approved boundaries. For a broader framework on production AI governance, see &lt;a href="https://subodhkc.com/blog/seven-layers-ai-compliance-nist-iso-soc2" rel="noopener noreferrer"&gt;seven layers of AI compliance&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Laws Actually Stand in August 2026
&lt;/h2&gt;

&lt;p&gt;Texas HB 149 took effect on &lt;strong&gt;January 1, 2026&lt;/strong&gt;. TRAIGA applies broadly to persons who conduct business in Texas, provide products or services used by Texas residents, or develop or deploy AI systems in the state. (&lt;a href="https://capitol.texas.gov/tlodocs/89R/billtext/html/HB00149F.htm" rel="noopener noreferrer"&gt;Texas Legislature Online&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The EU AI Act has followed a phased implementation. Prohibited practices and AI-literacy provisions began applying in February 2025. Governance rules and obligations for providers of general-purpose AI models began applying in August 2025. Article 50 transparency requirements became applicable on &lt;strong&gt;August 2, 2026&lt;/strong&gt;. (&lt;a href="https://digital-strategy.ec.europa.eu/en/policies/regulatory-framework-ai" rel="noopener noreferrer"&gt;Digital Strategy in Europe&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The EU's July 2026 AI Omnibus changed the high-risk timeline. High-risk AI systems in areas such as employment, education, biometrics, and critical infrastructure are now scheduled for their high-risk requirements from &lt;strong&gt;December 2, 2027&lt;/strong&gt;. High-risk systems embedded in regulated products have until &lt;strong&gt;August 2, 2028&lt;/strong&gt;. (&lt;a href="https://digital-strategy.ec.europa.eu/en/news/ai-omnibus-enters-force" rel="noopener noreferrer"&gt;Digital Strategy in Europe&lt;/a&gt;)&lt;br&gt;
RequirementStatus as of Aug. 8, 2026Texas TRAIGA / HB 149*&lt;em&gt;Effective since Jan. 1, 2026&lt;/em&gt;&lt;em&gt;EU prohibited AI practices&lt;/em&gt;&lt;em&gt;Applicable&lt;/em&gt;&lt;em&gt;EU AI-literacy obligations&lt;/em&gt;&lt;em&gt;Applicable&lt;/em&gt;&lt;em&gt;EU GPAI provider obligations&lt;/em&gt;&lt;em&gt;Applicable&lt;/em&gt;&lt;em&gt;EU Article 50 transparency&lt;/em&gt;&lt;em&gt;Applicable from Aug. 2, 2026&lt;/em&gt;*EU Annex III high-risk regimeDec. 2, 2027EU high-risk in Annex I regulated productsAug. 2, 2028&lt;br&gt;
An engineering team that believes the entire EU high-risk regime went live in August 2026 may build the wrong remediation plan. Urgency should come from what actually applies, not from a headline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Texas and Europe Are Solving Different Regulatory Problems
&lt;/h2&gt;

&lt;p&gt;Calling TRAIGA the Texas version of the EU AI Act is convenient and misleading. The EU AI Act creates a broad risk-based regulatory framework with four categories: prohibited uses, high-risk systems, systems subject to transparency obligations, and minimal or no-risk systems. (&lt;a href="https://digital-strategy.ec.europa.eu/en/policies/regulatory-framework-ai" rel="noopener noreferrer"&gt;Digital Strategy in Europe&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;TRAIGA does not create an equivalent general-purpose risk-tier system. Texas instead combines broad applicability, specific prohibited uses, particular disclosure requirements, biometric provisions, anti-discrimination rules, government-specific restrictions, an Attorney General enforcement mechanism, a cure process, and liability protections tied in part to testing and recognized risk-management frameworks.&lt;/p&gt;

&lt;p&gt;The first mistake in a compliance architecture would be trying to force Texas systems into an EU-style high-risk or low-risk classification and assuming the classification itself satisfies Texas law. It does not.&lt;br&gt;
DimensionTexas TRAIGA / HB 149EU AI ActPrimary structureProhibitions, duties, enforcement tied to specific usesBroad risk-based frameworkEffective stateIn force since Jan. 1, 2026Phased application underwayGeneral AI risk tiersNo equivalent EU-style tier systemProhibited, high-risk, transparency, minimalGovernment AI disclosureRequired for covered governmental agenciesDepends on applicable AI Act categoryHealthcare AI disclosureSpecific Texas disclosure requirementDepends on AI Act and other applicable EU lawDiscriminationIntentional unlawful discrimination prohibitedFundamental-rights protections in high-risk regimeCure processStatutory 60-day opportunity after AG noticeNo equivalent universal 60-day cure rightMain enforcerTexas Attorney GeneralNational authorities, AI Office, EDPSMaximum penaltiesTexas statutory civil penalty rangesUp to 35M EUR or 7% for certain violations&lt;/p&gt;

&lt;h2&gt;
  
  
  Texas HB 149 Is Narrower Than People Assume
&lt;/h2&gt;

&lt;p&gt;TRAIGA's disclosure obligation illustrates how summaries can mislead. The law specifically requires a covered &lt;strong&gt;governmental agency&lt;/strong&gt; making an AI system available to interact with consumers to disclose that the consumer is interacting with AI. It also establishes disclosure requirements when AI is used in connection with covered healthcare services or treatment. (&lt;a href="https://capitol.texas.gov/tlodocs/89R/billtext/html/HB00149F.htm" rel="noopener noreferrer"&gt;Texas Legislature Online&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That is materially different from a universal commercial chatbot disclosure requirement. The EU transparency regime is broader in different directions. As of August 2, 2026, Article 50 includes obligations around certain direct AI interactions and AI-generated or manipulated content. (&lt;a href="https://digital-strategy.ec.europa.eu/en/library/guidelines-transparency-obligations-providers-and-deployers-ai-systems" rel="noopener noreferrer"&gt;Digital Strategy in Europe&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;TRAIGA also does not prohibit AI bias in the abstract. The statute prohibits developing or deploying an AI system &lt;strong&gt;with the intent to unlawfully discriminate&lt;/strong&gt; against a protected class. Disparate impact alone is not sufficient by itself to demonstrate intent. (&lt;a href="https://capitol.texas.gov/tlodocs/89R/billtext/html/HB00149F.htm" rel="noopener noreferrer"&gt;Texas Legislature Online&lt;/a&gt;) An engineering dashboard claiming a fairness metric outside threshold equals a TRAIGA violation would be inventing a legal standard the statute does not contain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 60-Day Cure Period Makes Auditability Strategically Valuable
&lt;/h2&gt;

&lt;p&gt;TRAIGA's cure process is one of its most operationally important provisions. If the Texas Attorney General determines that a person has violated the law, the AG must provide written notice. The AG generally may not bring an action before the 60th day after that notice. If the organization cures the violation, it must provide a written statement, supporting documentation, and information about changes made to internal policies to prevent recurrence. (&lt;a href="https://capitol.texas.gov/tlodocs/89R/billtext/html/HB00149F.htm" rel="noopener noreferrer"&gt;Texas Legislature Online&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Read those requirements from an engineering perspective: identify the violation, remediate it, produce supporting documentation, change internal controls, and demonstrate how recurrence will be prevented. That is an incident-response workflow.&lt;/p&gt;

&lt;p&gt;Sixty days is generous only if you already know which system is implicated, who owns it, what version is deployed, which model is running, what data it uses, what controls were active, what changed, and how to reproduce the behavior. If your organization has to discover all of that after receiving the notice, much of the 60-day window will be spent reconstructing its own architecture.&lt;/p&gt;

&lt;p&gt;When investigating a complaint, the Texas AG may request information including the AI system's purpose, deployment context, training data, input categories, outputs, performance metrics, known limitations, post-deployment monitoring, user safeguards, and oversight processes. (&lt;a href="https://capitol.texas.gov/tlodocs/89R/billtext/html/HB00149F.htm" rel="noopener noreferrer"&gt;Texas Legislature Online&lt;/a&gt;) That is close to a specification for the minimum AI system record you should maintain internally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Texas Enforcement and Penalties
&lt;/h2&gt;

&lt;p&gt;TRAIGA gives the Texas Attorney General primary enforcement authority and does not create a private right of action. For uncured violations, the law provides different civil-penalty ranges: &lt;strong&gt;$10,000 to $12,000&lt;/strong&gt; for each violation a court determines to be curable, &lt;strong&gt;$80,000 to $200,000&lt;/strong&gt; for each violation determined to be uncurable, and &lt;strong&gt;$2,000 to $40,000 per day&lt;/strong&gt; for a continuing violation. The AG can also seek injunctive relief and recover specified costs. (&lt;a href="https://capitol.texas.gov/tlodocs/89R/billtext/html/HB00149F.htm" rel="noopener noreferrer"&gt;Texas Legislature Online&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The EU AI Act's penalty structure is on a different scale: up to &lt;strong&gt;35 million EUR or 7% of worldwide annual turnover&lt;/strong&gt; for certain prohibited-practice violations, up to &lt;strong&gt;15 million EUR or 3%&lt;/strong&gt; for a range of other obligations, and up to &lt;strong&gt;7.5 million EUR or 1%&lt;/strong&gt; for supplying incorrect or misleading information to authorities. Lower comparative thresholds apply to SMEs. (&lt;a href="https://digital-strategy.ec.europa.eu/en/faqs/navigating-ai-act" rel="noopener noreferrer"&gt;Digital Strategy in Europe&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The numbers differ. The architectural lesson does not. Evidence matters.&lt;/p&gt;

&lt;p&gt;consider two companies receive a Texas notice on the same day. Company A has no AI inventory, shared API keys, unversioned prompts, unknown model revisions, no deployment evidence, no red-team history, and no owner map. Company B has a system inventory, deployment history, versioned control results, known owners, model lineage, evaluation history, a change log, and documented monitoring. Both receive the same statutory period. They do not have the same amount of time. Company B spends its time correcting the problem. Company A spends its time discovering what exists. Compliance engineering converts regulatory response from archaeology into incident management.&lt;/p&gt;

&lt;h2&gt;
  
  
  NIST Gives Texas Organizations Something Concrete
&lt;/h2&gt;

&lt;p&gt;TRAIGA contains an important connection to the &lt;strong&gt;NIST Artificial Intelligence Risk Management Framework: Generative AI Profile&lt;/strong&gt;. The law's liability section addresses situations in which a defendant discovers a violation through mechanisms including feedback, adversarial or red-team testing, or an internal review process associated with substantial compliance with the current NIST Generative AI Profile or another nationally or internationally recognized AI risk-management framework. (&lt;a href="https://capitol.texas.gov/tlodocs/89R/billtext/html/HB00149F.htm" rel="noopener noreferrer"&gt;Texas Legislature Online&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Substantial compliance with NIST does not mean every action your company takes is automatically TRAIGA-compliant. The statutory protection is more specific and should be interpreted with counsel. But Texas has explicitly connected recognized AI-risk-management practices, internal review, and testing to its liability framework. That is a significant engineering signal.&lt;/p&gt;

&lt;p&gt;NIST does not describe the AI RMF as a checklist. The framework is organized around four functions: GOVERN, MAP, MEASURE, and MANAGE. NIST explicitly says the activities are not necessarily sequential checklist steps. Risk management is intended to be continuous across the AI lifecycle. (&lt;a href="https://airc.nist.gov/airmf-resources/airmf/5-sec-core/" rel="noopener noreferrer"&gt;NIST AI Resource Center&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That makes it more useful for engineering. GOVERN defines who owns the system and which policies apply. MAP documents what the system does and who is affected. MEASURE evaluates whether identified risks are materializing. MANAGE determines which controls are required and what happens when they fail. For a deeper operational framework, see &lt;a href="https://subodhkc.com/blog/haiec-modular-ai-governance-framework" rel="noopener noreferrer"&gt;the HAIEC modular governance framework&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The EU AI Act Asks for a More Formal Lifecycle
&lt;/h2&gt;

&lt;p&gt;When the relevant high-risk requirements begin applying under the updated timeline, providers of covered high-risk systems face a considerably more prescriptive structure. The Commission summarizes requirements including risk assessment and mitigation, data-quality requirements, activity logging, technical documentation, information for deployers, human oversight, robustness, cybersecurity, and accuracy. (&lt;a href="https://digital-strategy.ec.europa.eu/en/policies/regulatory-framework-ai" rel="noopener noreferrer"&gt;Digital Strategy in Europe&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Providers must retain certain technical documentation for &lt;strong&gt;ten years&lt;/strong&gt;, and automatically generated logs under their control must generally be kept for at least &lt;strong&gt;six months&lt;/strong&gt; unless other applicable law provides differently. (&lt;a href="https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=celex%3A32024R1689" rel="noopener noreferrer"&gt;EUR-Lex&lt;/a&gt;) The Regulation also requires providers to establish and document post-market monitoring designed to collect and analyze performance information across the system's lifetime so continued compliance can be evaluated.&lt;/p&gt;

&lt;p&gt;Those are not merely policy-writing requirements. They create system-design consequences: logging architecture, retention policies, documentation pipelines, and monitoring systems must all be built to satisfy these obligations.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Inventory Is the Foundation
&lt;/h2&gt;

&lt;p&gt;Before building an AI compliance CI/CD gate, build an AI inventory. A minimal system record should be machine-readable, versioned, and attributable:&lt;br&gt;
system_id: customer-support-assistant&lt;br&gt;
owner:&lt;br&gt;
  business: customer_operations&lt;br&gt;
  technical: platform_ai&lt;br&gt;
jurisdictions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;texas&lt;/li&gt;
&lt;li&gt;european_union
role:
texas:
deployer: true
developer: false
eu:
provider: false
deployer: true
purpose:
approved:

&lt;ul&gt;
&lt;li&gt;answer_customer_questions&lt;/li&gt;
&lt;li&gt;retrieve_order_status
prohibited_use:&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;autonomous_credit_decision&lt;/li&gt;
&lt;li&gt;employment_screening
model:
provider: example-provider
model_id: model-x
revision: "2026-07-21"
data:
personal_data: true
biometric_data: false
retrieval_sources:

&lt;ul&gt;
&lt;li&gt;approved_support_kb
human_oversight:
escalation_required: true
monitoring:
latency: enabled
safety_evaluations: enabled
output_sampling: enabled
evidence:
risk_review_id: RR-2026-184
deployment_id: DEP-8371
If the organization cannot answer which AI systems are currently affecting Texans or EU users, the rest of the compliance program is operating on assumptions. For production readiness fundamentals, see &lt;a href="https://subodhkc.com/blog/12-production-readiness-checks-for-ai-pilots" rel="noopener noreferrer"&gt;12 production readiness checks for AI pilots&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Compliance Becomes Architecture: Moving Governance Into the Deployment Path
&lt;/h2&gt;

&lt;p&gt;Most companies add AI governance alongside their CI/CD pipeline as a separate process. Legal writes a PDF. Engineering deploys code. The two systems barely interact.&lt;br&gt;
             AI SYSTEM RECORD&lt;br&gt;
                    |&lt;br&gt;
                    v&lt;br&gt;
CODE --&amp;gt; BUILD --&amp;gt; CONTROL EVALUATION&lt;br&gt;
                    |&lt;br&gt;
                    +--&amp;gt; Security tests&lt;br&gt;
                    +--&amp;gt; Risk tests&lt;br&gt;
                    +--&amp;gt; Disclosure requirements&lt;br&gt;
                    +--&amp;gt; Human-oversight controls&lt;br&gt;
                    +--&amp;gt; Model/version checks&lt;br&gt;
                    +--&amp;gt; Jurisdiction rules&lt;br&gt;
                    +--&amp;gt; Evidence generation&lt;br&gt;
                    |&lt;br&gt;
                    v&lt;br&gt;
               RELEASE GATE&lt;br&gt;
               /          \&lt;br&gt;
            PASS          FAIL&lt;br&gt;
             |              |&lt;br&gt;
             v              v&lt;br&gt;
          DEPLOY        REMEDIATE&lt;br&gt;
             |&lt;br&gt;
             v&lt;br&gt;
      POST-DEPLOY MONITORING&lt;br&gt;
             |&lt;br&gt;
             v&lt;br&gt;
       VERSIONED EVIDENCE&lt;br&gt;
Neither TRAIGA nor the EU AI Act mandates this exact CI/CD architecture. The benefit is that legal requirements stop existing only as prose. They become testable conditions in the deployment path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deterministic Gates vs Legal Judgments
&lt;/h2&gt;

&lt;p&gt;Some compliance questions can become deterministic gates. Suppose a healthcare AI service in Texas requires a disclosure before the relevant service is provided. A deployment can fail if &lt;code&gt;healthcare_ai = true&lt;/code&gt; and &lt;code&gt;disclosure_enabled = false&lt;/code&gt;. That is deterministic. No language model needs to decide whether it feels compliant.&lt;/p&gt;

&lt;p&gt;But not every legal question should become a Boolean. Texas's prohibition on intentionally using AI to unlawfully discrimination cannot be reduced to &lt;code&gt;if fairness_score &amp;gt; 0.91: traiga_compliant = True&lt;/code&gt;. Instead, the system can verify that required governance steps were performed: approved use case, protected class review completed, fairness evaluation completed, legal review approved, model version tested. The tool proves the workflow happened. It does not pretend the mathematical test conclusively answered the legal question.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Right Unit of Automation Is the Control, Not the Law
&lt;/h2&gt;

&lt;p&gt;Instead of one giant &lt;code&gt;EU_AI_ACT = PASS&lt;/code&gt; or &lt;code&gt;TRAIGA = 92%&lt;/code&gt; without being able to explain the denominator, the architecture should trace a chain:&lt;br&gt;
Law&lt;br&gt;
  |&lt;br&gt;
  v&lt;br&gt;
Requirement&lt;br&gt;
  |&lt;br&gt;
  v&lt;br&gt;
Applicability decision&lt;br&gt;
  |&lt;br&gt;
  v&lt;br&gt;
Control&lt;br&gt;
  |&lt;br&gt;
  v&lt;br&gt;
Technical check&lt;br&gt;
  |&lt;br&gt;
  v&lt;br&gt;
Evidence&lt;br&gt;
For example: EU Article 50, then AI-interaction transparency applies, then UI disclosure required, then disclosure test, then PASS, then screenshot plus test result plus build hash. The compliance conclusion remains a governance decision. The technical result becomes reproducible.&lt;/p&gt;

&lt;p&gt;A deterministic gate for a healthcare AI disclosure in Texas might look like:&lt;br&gt;
control_id: TX-AI-DISCLOSURE-HEALTHCARE&lt;/p&gt;

&lt;p&gt;applies_when:&lt;br&gt;
  jurisdiction: texas&lt;br&gt;
  healthcare_ai: true&lt;/p&gt;

&lt;p&gt;require:&lt;br&gt;
  disclosure_enabled: true&lt;br&gt;
  disclosure_version: approved&lt;br&gt;
  disclosure_event_test: passed&lt;br&gt;
Now a deployment fails if &lt;code&gt;healthcare_ai = true&lt;/code&gt; and &lt;code&gt;disclosure_enabled = false&lt;/code&gt;. No language model needs to decide whether it feels compliant. A lawyer or governance owner determines the rule. The pipeline verifies whether the agreed technical condition exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deployment Gate I Would Build
&lt;/h2&gt;

&lt;p&gt;def evaluate_release(system, deployment, controls):&lt;br&gt;
    failures = []&lt;br&gt;
    if not system.owner:&lt;br&gt;
        failures.append('AI system owner missing')&lt;br&gt;
    if not deployment.model_revision:&lt;br&gt;
        failures.append('Model revision not recorded')&lt;br&gt;
    for control in controls.required_for(system):&lt;br&gt;
        result = control.evaluate(system=system, deployment=deployment)&lt;br&gt;
        evidence.store(result)&lt;br&gt;
        if result.blocking and not result.passed:&lt;br&gt;
            failures.append(control.id)&lt;br&gt;
    if failures:&lt;br&gt;
        return {'decision': 'BLOCK', 'reasons': failures}&lt;br&gt;
    return {'decision': 'ALLOW', 'evidence_id': evidence.bundle_id}&lt;br&gt;
The release decision and the evidence supporting it should be generated from the same control execution. Not reconstructed months later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do Not Let Compliance Automation Blindly Stop Deployment
&lt;/h2&gt;

&lt;p&gt;A governance gate that can block production becomes a production dependency. It needs its own availability design, versioning, rollback, exception process, separation of duties, change approval, and audit history. Suppose a compliance rule is incorrectly configured and blocks every production deployment on Friday afternoon. You now have a governance incident.&lt;/p&gt;

&lt;p&gt;An exception should not mean turning compliance off. It should mean: named owner, reason, approver, scope, expiration, and compensating control. That is how ordinary enterprise change management already works. AI governance does not need to reinvent it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AI Can Still Help
&lt;/h2&gt;

&lt;p&gt;None of this means compliance engineering should avoid AI. AI can classify systems for review, summarize regulatory changes, suggest likely control mappings, analyze policy documents, help investigators search logs, identify missing evidence, generate test cases, and assist with red-team exploration.&lt;/p&gt;

&lt;p&gt;But three responsibilities should remain distinct: AI assists interpretation, deterministic system enforces approved control, and qualified human owns legal conclusion. Conflating them is how a compliance assistant quietly becomes a compliance authority.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drift Detection: Configuration Before Statistics
&lt;/h2&gt;

&lt;p&gt;At least four things can drift: model drift (provider version changes), data drift (input distribution changes), behavior drift (outputs change under same evaluation), and configuration drift (prompts, tools, retrieval sources, safety controls change).&lt;/p&gt;

&lt;p&gt;For many LLM applications, the highest-value monitoring does not require a sophisticated ML drift algorithm. Start with configuration drift:&lt;br&gt;
Yesterday:&lt;br&gt;
model = x-2026-07&lt;br&gt;
prompt_hash = A84...&lt;br&gt;
retrieval_index = v12&lt;br&gt;
tools = [search, booking]&lt;br&gt;
temperature = 0.2&lt;/p&gt;

&lt;p&gt;Today:&lt;br&gt;
model = x-2026-08&lt;br&gt;
prompt_hash = 9BC...&lt;br&gt;
retrieval_index = v13&lt;br&gt;
tools = [search, booking, refund]&lt;br&gt;
temperature = 0.7&lt;br&gt;
That is a material change detected deterministically. A deployment gate can require rerunning evaluations when configuration changes. In the EU high-risk framework, post-market monitoring must actively and systematically collect and analyze performance data across the system's lifetime. (&lt;a href="https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=celex%3A32024R1689" rel="noopener noreferrer"&gt;EUR-Lex&lt;/a&gt;) Drift detection supports that process. It is not synonymous with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence Should Be Generated When the System Changes
&lt;/h2&gt;

&lt;p&gt;A common compliance architecture produces evidence once per audit. That is backwards for software. Most meaningful evidence is easiest to produce when the change happens. At deployment time, capture: system identifier, application version, model provider and revision, prompt and configuration version, tool and retrieval configuration, control-set version, test results, required disclosures, approval identities, deployment timestamp, artifact hashes, exceptions, and known limitations.&lt;/p&gt;

&lt;p&gt;A stronger evidence artifact tells you: control ID, result, system, deployment, commit hash, model version, rule version, test file, timestamp, executor, and artifact hash. The question becomes answerable precisely: this exact deployment passed this exact version of the control at this time. That is the difference between documentation and provenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Board Does Not Need Another AI Risk Score
&lt;/h2&gt;

&lt;p&gt;Most AI governance dashboards present a single composite score: &lt;em&gt;AI Governance Score: 87&lt;/em&gt;. That number does not tell a board member anything useful. It does not tell a CTO what to fix. It does not tell an auditor what was checked. It does not tell a regulator what evidence exists.&lt;/p&gt;

&lt;p&gt;A stronger dashboard answers specific questions: Which systems are deployed in Texas? Which have completed disclosure tests? Which have pending exceptions? Which have unresolved drift alerts? Which have overdue evaluations? Which have missing owners? Which have model changes since last approval?&lt;/p&gt;

&lt;p&gt;The composite score is a communication artifact. The specific answers are an operating artifact. Boards need both. Engineering teams need the second one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not Every Model Update Should Be Treated Equally
&lt;/h2&gt;

&lt;p&gt;A model version change from &lt;code&gt;x-2026-07&lt;/code&gt; to &lt;code&gt;x-2026-08&lt;/code&gt; might be a patch release with no behavioral change or a major release with new capabilities. A prompt change might add a clarification or remove a safety instruction. A retrieval index update might add new documents or remove a critical policy document.&lt;br&gt;
change_classification:&lt;br&gt;
  model:&lt;br&gt;
    patch: rerun_safety_eval&lt;br&gt;
    minor: rerun_safety_eval + fairness_eval&lt;br&gt;
    major: full_reapproval&lt;br&gt;
  prompt:&lt;br&gt;
    cosmetic: log_only&lt;br&gt;
    behavioral: rerun_eval&lt;br&gt;
    safety_control_removed: full_reapproval&lt;br&gt;
  retrieval:&lt;br&gt;
    add_documents: log_only&lt;br&gt;
    remove_documents: rerun_eval&lt;br&gt;
    policy_change: full_reapproval&lt;br&gt;
The point is not the exact classification. The point is that a deployment gate should treat material changes differently from cosmetic ones. A blanket rule that blocks every change creates noise. A blanket rule that allows every change creates risk. The classification itself should be governed and versioned.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical 10-Control Starting Set
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI inventory completeness&lt;/strong&gt;: Every production AI system has owner, purpose, jurisdiction, model, data classes, deployment context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role classification&lt;/strong&gt;: For each jurisdiction, determine developer, provider, deployer, government entity, or healthcare provider status.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approved-use boundary&lt;/strong&gt;: The system has explicitly allowed and prohibited uses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model provenance&lt;/strong&gt;: Record provider, model, and version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Material-change detection&lt;/strong&gt;: Detect changes to models, prompts, tools, retrieval, or safety configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Required disclosure test&lt;/strong&gt;: Where applicable, verify actual user-facing behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation evidence&lt;/strong&gt;: Required safety and performance tests execute before deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human oversight&lt;/strong&gt;: Where a workflow requires human control, prove the control exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-deployment monitoring&lt;/strong&gt;: Track metrics and limitations tied to the system's risk profile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incident and remediation history&lt;/strong&gt;: When a control fails, record what happened, what changed, who approved it, and what proves remediation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These ten controls will not make an organization automatically compliant. They will make it substantially easier to operate a serious compliance program. For immutable audit trail patterns, see &lt;a href="https://subodhkc.com/blog/implementing-immutable-audit-trails-for-soc-2-ai-compliance" rel="noopener noreferrer"&gt;implementing immutable audit trails for SOC 2 AI compliance&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Cross-Jurisdictional Control Architecture
&lt;/h2&gt;

&lt;p&gt;Organizations operating in both Texas and the EU should not build two separate compliance programs. They should build one control layer with jurisdiction-specific applicability rules.&lt;br&gt;
     SHARED AI SYSTEM RECORD&lt;br&gt;
              |&lt;br&gt;
              v&lt;br&gt;
     SHARED CONTROL LAYER&lt;br&gt;
     /                    \&lt;br&gt;
  TEXAS RULES          EU RULES&lt;br&gt;
     |                    |&lt;br&gt;
  TX disclosure       EU transparency&lt;br&gt;
  TX cure process     EU high-risk&lt;br&gt;
  TX AG inquiry       EU post-market&lt;br&gt;
     |                    |&lt;br&gt;
     +--------+-----------+&lt;br&gt;
              |&lt;br&gt;
              v&lt;br&gt;
     SHARED EVIDENCE STORE&lt;br&gt;
              |&lt;br&gt;
              v&lt;br&gt;
     JURISDICTION-SPECIFIC&lt;br&gt;
     REPORTING OUTPUTS&lt;br&gt;
The controls are shared. The applicability decisions differ. The evidence is shared. The reporting outputs differ. This avoids duplicating engineering work while respecting that the legal requirements are not identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changes Between Texas and Europe
&lt;/h2&gt;

&lt;p&gt;Both jurisdictions ultimately require you to know what AI you operate, why it is permitted, what changed, and what evidence exists. The emphasis differs:&lt;br&gt;
CapabilityTexas emphasisEU emphasisInventory and ownershipAG inquiry responseTechnical documentationRisk managementNIST alignment, testing, internal reviewFormal risk management systemDisclosureGovernment and healthcare contextsArticle 50 transparency, broader scopeMonitoringPost-deployment, feedback, red-team testingPost-market monitoring, systematic collectionEvidence retentionSupporting documentation for cure10-year documentation, 6-month logsEnforcementAG, cure process, civil penaltiesNational authorities, AI Office, tiered fines&lt;br&gt;
NIST can become the common operational language. TRAIGA explicitly references the NIST Generative AI Profile. The EU AI Act references risk management systems and conformity assessment but does not mandate NIST specifically. An organization that builds its control architecture around NIST GOVERN, MAP, MEASURE, and MANAGE can map those controls to both Texas and EU requirements without maintaining two separate frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compliance Becomes More Difficult When AI Becomes Agentic
&lt;/h2&gt;

&lt;p&gt;Most current AI compliance assumes a relatively static system: one model, one prompt, one retrieval source, one deployment. Agentic AI systems break that assumption. An agent might call multiple models, use different tools depending on context, modify its own prompts, select retrieval sources dynamically, and chain calls in ways that were not explicitly designed.&lt;/p&gt;

&lt;p&gt;The compliance question shifts from &lt;em&gt;did we approve this system?&lt;/em&gt; to &lt;em&gt;is this system still operating within its approved boundaries?&lt;/em&gt; That requires runtime monitoring, not just deployment-time gates. The system graph, not the spreadsheet, becomes the compliance artifact.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future Compliance Artifact Is a Deployment Graph
&lt;/h2&gt;

&lt;p&gt;When an auditor asks &lt;em&gt;show me the evidence for system X on date Y&lt;/em&gt;, the answer should not require reconstructing a year of Slack messages and Jira tickets. The answer should be a query against a deployment graph: system, version, model, controls, results, evidence, exceptions, approvals, and timeline.&lt;/p&gt;

&lt;p&gt;That graph is built incrementally. Every deployment adds nodes. Every control execution adds edges. Every exception adds metadata. The graph becomes the living compliance record. The audit becomes a traversal.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Test for Whether an AI Control Belongs in CI/CD
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Can the condition be evaluated deterministically from system configuration, deployment metadata, or test results?&lt;/li&gt;
&lt;li&gt;Does failing the condition create a real compliance or safety risk, not just a documentation gap?&lt;/li&gt;
&lt;li&gt;Can the check be versioned, reviewed, and rolled back like any other code change?&lt;/li&gt;
&lt;li&gt;Can an exception be granted with named owner, reason, approver, scope, and expiration?&lt;/li&gt;
&lt;li&gt;Does the check produce evidence that an auditor or regulator would accept?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer to all five is yes, the control belongs in the deployment gate. If not, it belongs in a governance workflow that feeds into the gate but does not block it directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Difference Between a Policy Program and a Compliance System
&lt;/h2&gt;

&lt;p&gt;A policy program says: &lt;em&gt;We have a policy that requires AI risk assessment before deployment.&lt;/em&gt; A compliance system says: &lt;em&gt;Deployment DEP-8371 passed control TX-AI-DISCLOSURE-HEALTHCARE v3.2 at 2026-08-08T14:03:22Z, evidence stored as EV-4471, approved by j.smith, model revision x-2026-08, no active exceptions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first statement is necessary. The second statement is what an auditor, a regulator, and a court will actually ask for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 90-Day CTO Playbook
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Days 1-30: Know what exists.&lt;/strong&gt; Build the inventory. For every production AI system, identify owner, use, geography, role, model and provider, affected people, data, autonomy, integrations, and existing controls. Do not begin by buying a compliance dashboard. Begin by establishing ground truth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Days 31-60: Map obligations to controls.&lt;/strong&gt; Work with legal, security, and product to create the chain: Requirement, then Applicability, then Control, then Owner, then Evidence. Start with currently applicable requirements and the highest-risk use cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Days 61-90: Move controls into engineering.&lt;/strong&gt; Add model and configuration versioning, deployment-linked evaluations, disclosure tests, change detection, approval gates, exceptions, evidence generation, and post-deployment alerts.&lt;/p&gt;

&lt;p&gt;The result after 90 days will not be finished compliance. That is the wrong target. The result should be a system that makes continued compliance easier to operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Strongest Compliance Architecture
&lt;/h2&gt;

&lt;p&gt;Texas HB 149 and the EU AI Act are not two versions of the same law. TRAIGA is comparatively targeted: specific duties, prohibited practices, AG enforcement, a defined cure process, and an unusual connection between testing and recognized AI risk-management frameworks. The EU AI Act is broader lifecycle regulation with a formal risk-based structure, transparency rules already applicable, GPAI obligations underway, and a high-risk regime whose major technical requirements now arrive later under the 2026 AI Omnibus.&lt;/p&gt;

&lt;p&gt;Strip away the legal vocabulary and look at both from the system architecture layer. The questions converge: What AI do we operate? Why is this use permitted? Which model and configuration are actually deployed? Which requirements apply? Which controls enforce them? What changed? Did the required tests run? What happened after deployment? Can we reproduce the evidence? Who approved the exceptions?&lt;/p&gt;

&lt;p&gt;Those questions cannot be answered reliably by an annual spreadsheet. They have to become part of the engineering lifecycle. The strongest AI compliance architecture is not the one with the longest checklist. It is the one that can connect a legal requirement to a technical control, a technical control to a specific deployment, and that deployment to evidence you can still reproduce when somebody finally asks for it.&lt;/p&gt;

&lt;p&gt;If you are operating AI systems across Texas and EU jurisdictions and need an independent systems advisor to audit your compliance architecture, &lt;a href="https://subodhkc.com/advisory" rel="noopener noreferrer"&gt;schedule a strategic evaluation&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://subodhkc.com/blog/texas-hb-149-eu-ai-act-engineering-compliance-playbook" rel="noopener noreferrer"&gt;subodhkc.com&lt;/a&gt;. Learn more about the &lt;a href="https://haiec.com" rel="noopener noreferrer"&gt;HAIEC AI Governance Platform&lt;/a&gt;. Follow for more on AI governance, enterprise architecture, and compliance engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>texasailawhb149compl</category>
      <category>euaiactengineeringfr</category>
      <category>nistairmfimplementat</category>
      <category>traigacompliance</category>
    </item>
    <item>
      <title>FastAPI WebSocket 1006 in LLM Streaming: Why and How to Fix</title>
      <dc:creator>Subodh Kc</dc:creator>
      <pubDate>Tue, 29 Sep 2026 15:18:09 +0000</pubDate>
      <link>https://dev.to/subodhkc/fastapi-websocket-1006-in-llm-streaming-why-and-how-to-fix-1b2h</link>
      <guid>https://dev.to/subodhkc/fastapi-websocket-1006-in-llm-streaming-why-and-how-to-fix-1b2h</guid>
      <description>&lt;h2&gt;
  
  
  The Symptom: WebSocket Closed Before the LLM Finished
&lt;/h2&gt;

&lt;p&gt;Your FastAPI WebSocket opens normally. The client sends a prompt. The model begins a long inference operation. Nothing comes back for a while. Then the browser reports:&lt;br&gt;
WebSocket closed&lt;br&gt;
code: 1006&lt;br&gt;
reason: ""&lt;br&gt;
Code 1006 is not a close code sent by FastAPI. RFC 6455 reserves 1006 for abnormal closure where a proper WebSocket Close frame was not received. An endpoint must not put 1006 into a Close control frame. (&lt;a href="https://www.rfc-editor.org/info/rfc6455" rel="noopener noreferrer"&gt;RFC Editor&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That means &lt;strong&gt;1006 tells you the connection disappeared without a clean handshake&lt;/strong&gt;. It does not tell you which layer disappeared first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Diagnostic Fork: Four Failure Classes
&lt;/h2&gt;

&lt;p&gt;Before changing any timeout, determine which of these four failures you actually have:&lt;br&gt;
Browser / Mobile / Voice Client&lt;br&gt;
              |&lt;br&gt;
              v&lt;br&gt;
        CDN / Edge&lt;br&gt;
              |&lt;br&gt;
              v&lt;br&gt;
     Load Balancer / WAF&lt;br&gt;&lt;br&gt;
The fastest debugging signal is &lt;strong&gt;timing&lt;/strong&gt;. Measure the gap between last successful traffic and disconnect:&lt;br&gt;
PatternInvestigate FirstDisconnect varies wildlyNetwork, client state, process healthConsistently around 20-40 secPing/pong and WebSocket liveness configConsistently around 60 secProxy or load-balancer idle policyOnly during synchronous model callsEvent-loop blockingOnly under high concurrencyEvent-loop lag, CPU saturation, backpressureServer logs clean close but client says 1006Trace intermediary or networkServer reports ping timeoutTransport latency or event-loop responsiveness&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Common Fix Is Wrong
&lt;/h2&gt;

&lt;p&gt;Search for this problem and you will find a confident explanation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;FastAPI automatically kills WebSockets when the LLM takes longer than 10 seconds.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is wrong. FastAPI does not have a built-in 10-second LLM generation timeout. Current Uvicorn defaults are 20 seconds for &lt;code&gt;--ws-ping-interval&lt;/code&gt; and 20 seconds for &lt;code&gt;--ws-ping-timeout&lt;/code&gt;. (&lt;a href="https://www.uvicorn.org/settings/" rel="noopener noreferrer"&gt;Uvicorn&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The common proposed fix is a JSON heartbeat daemon:&lt;br&gt;
async def websocket_ping_daemon(websocket):&lt;br&gt;
    while True:&lt;br&gt;
        await asyncio.sleep(5)&lt;br&gt;
        await websocket.send_json({"type": "ping"})&lt;br&gt;
This has good instincts but three critical problems. First, &lt;code&gt;{"type": "ping"}&lt;/code&gt; is an application data message, not a WebSocket protocol Ping control frame. Second, if the event loop is blocked by a synchronous call, the heartbeat cannot run either. Third, it does not address proxy or load-balancer idle timeouts that operate independently of application-level traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Class 1: You Are Blocking the Event Loop
&lt;/h2&gt;

&lt;p&gt;This is the failure that most closely matches the original theory. Consider two operations that both take 20 seconds:&lt;/p&gt;

&lt;h1&gt;
  
  
  Genuinely async - event loop stays responsive
&lt;/h1&gt;

&lt;p&gt;result = await async_llm_client.generate(prompt)&lt;/p&gt;

&lt;h1&gt;
  
  
  Blocking - event loop frozen for 20 seconds
&lt;/h1&gt;

&lt;p&gt;result = synchronous_llm_client.generate(prompt)&lt;br&gt;
With a genuinely asynchronous operation, &lt;code&gt;await&lt;/code&gt; gives control back to the event loop. During those 20 seconds, the event loop services other WebSockets, ping/pong handling, timers, and HTTP requests. A synchronous blocking call inside an async handler freezes everything. (&lt;a href="https://docs.python.org/3/library/asyncio-task.html" rel="noopener noreferrer"&gt;Python documentation&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The same principle applies to &lt;code&gt;time.sleep()&lt;/code&gt; versus &lt;code&gt;asyncio.sleep()&lt;/code&gt;. The problem is not sleep. The problem is blocking the event loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix A: Use the async SDK
&lt;/h3&gt;

&lt;p&gt;async for token in async_model.stream(prompt):&lt;br&gt;
    await websocket.send_text(token)&lt;br&gt;
Long latency is not automatically event-loop starvation. A model can take 30 seconds before producing output without freezing the event loop, provided the operation is implemented asynchronously.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix B: Wrap synchronous SDKs with asyncio.to_thread
&lt;/h3&gt;

&lt;p&gt;result = await asyncio.to_thread(&lt;br&gt;
    blocking_model_call,&lt;br&gt;
    prompt,&lt;br&gt;
)&lt;br&gt;
Python documents &lt;code&gt;asyncio.to_thread()&lt;/code&gt; specifically for synchronous I/O that would otherwise block the event loop. (&lt;a href="https://docs.python.org/3/library/asyncio-task.html" rel="noopener noreferrer"&gt;Python documentation&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix C: Move CPU-bound work to a process pool
&lt;/h3&gt;

&lt;p&gt;For local inference, embedding post-processing, or expensive parsing, &lt;code&gt;asyncio.to_thread()&lt;/code&gt; is limited by CPython's GIL. Use a process pool or a dedicated compute service. Your WebSocket process should not quietly become your compute scheduler. For production voice and real-time AI patterns, see &lt;a href="https://subodhkc.com/blog/ai-voice-agent-architecture-kestrelvoice" rel="noopener noreferrer"&gt;voice agent architecture at Kestrel Voice&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Class 2: Uvicorn WebSocket Ping Timeout
&lt;/h2&gt;

&lt;p&gt;Your model request may be correctly asynchronous and the process responsive, but the WebSocket transport still fails ping/pong health checks. Server logs may show:&lt;br&gt;
websockets.exceptions.ConnectionClosedError:&lt;br&gt;
sent 1011 (internal error) keepalive ping timeout;&lt;br&gt;
no close frame received&lt;br&gt;
The &lt;code&gt;websockets&lt;/code&gt; library documents this as a keepalive timeout. (&lt;a href="https://websockets.readthedocs.io/en/stable/faq/connection.html" rel="noopener noreferrer"&gt;websockets&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Parameter Breakdown
&lt;/h3&gt;

&lt;p&gt;ParameterDefaultControlsWhen to Change&lt;code&gt;--ws-ping-interval&lt;/code&gt;20.0sHow often Uvicorn sends WebSocket Ping framesIncrease if ping traffic is excessive on constrained networks&lt;code&gt;--ws-ping-timeout&lt;/code&gt;20.0sHow long Uvicorn waits for a Pong before closingIncrease only after confirming event loop is healthy&lt;code&gt;--timeout-keep-alive&lt;/code&gt;5.0sHTTP keep-alive timeout (not WebSocket)Do not change for WebSocket 1006 issuesNGINX &lt;code&gt;proxy_read_timeout&lt;/code&gt;60sTime NGINX waits between reads from upstreamIncrease for long LLM generation gapsAWS ALB idle timeout60sTime ALB waits with no traffic from either sideAlign with your maximum expected silent period&lt;br&gt;
Do not confuse &lt;code&gt;--timeout-keep-alive&lt;/code&gt; with WebSocket ping settings. They are separate timeout classes documented separately by Uvicorn. (&lt;a href="https://github.com/encode/uvicorn/blob/master/docs/settings.md" rel="noopener noreferrer"&gt;Uvicorn settings&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Class 3: Proxy or Load-Balancer Idle Timeout
&lt;/h2&gt;

&lt;p&gt;If disconnects happen consistently near 60 seconds, inspect infrastructure before rewriting the application. NGINX defaults &lt;code&gt;proxy_read_timeout&lt;/code&gt; to 60 seconds. (&lt;a href="https://nginx.org/en/docs/http/ngx_http_proxy_module.html" rel="noopener noreferrer"&gt;Nginx&lt;/a&gt;) AWS Application Load Balancers default their idle timeout to 60 seconds. (&lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html" rel="noopener noreferrer"&gt;AWS Documentation&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;A WebSocket-aware NGINX configuration:&lt;br&gt;
map $http_upgrade $connection_upgrade {&lt;br&gt;
    default upgrade;&lt;br&gt;
    ''      close;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;location /stream {&lt;br&gt;
    proxy_pass &lt;a href="http://fastapi_upstream" rel="noopener noreferrer"&gt;http://fastapi_upstream&lt;/a&gt;;&lt;br&gt;
    proxy_http_version 1.1;&lt;br&gt;
    proxy_set_header Upgrade $http_upgrade;&lt;br&gt;
    proxy_set_header Connection $connection_upgrade;&lt;br&gt;
    proxy_read_timeout 300s;&lt;br&gt;
}&lt;br&gt;
The &lt;code&gt;websockets&lt;/code&gt; project notes that real HTTP infrastructure commonly closes connections after idle periods in the 30-120 second range. (&lt;a href="https://websockets.readthedocs.io/en/14.2/topics/keepalive.html" rel="noopener noreferrer"&gt;websockets&lt;/a&gt;) When a disconnect occurs at a repeatable infrastructure-sized interval, inspect infrastructure first. For broader production architecture patterns, see &lt;a href="https://subodhkc.com/blog/production-rag-architecture-patterns-for-hybrid-search" rel="noopener noreferrer"&gt;production RAG architecture patterns&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Configuration: Async Streaming with Serialized Heartbeat
&lt;/h2&gt;

&lt;p&gt;Once the event loop is healthy, add an application heartbeat for session liveness and proxy traffic. Serialize all outbound writes with a lock to prevent interleaved frames:&lt;br&gt;
import asyncio&lt;br&gt;
import time&lt;/p&gt;

&lt;p&gt;from fastapi import FastAPI, WebSocket, WebSocketDisconnect&lt;/p&gt;

&lt;p&gt;app = FastAPI()&lt;/p&gt;

&lt;p&gt;async def send_json_safe(websocket, lock, payload):&lt;br&gt;
    async with lock:&lt;br&gt;
        await websocket.send_json(payload)&lt;/p&gt;

&lt;p&gt;async def application_heartbeat(websocket, lock, interval=15.0):&lt;br&gt;
    while True:&lt;br&gt;
        await asyncio.sleep(interval)&lt;br&gt;
        await send_json_safe(websocket, lock, {&lt;br&gt;
            "type": "heartbeat",&lt;br&gt;
            "timestamp": time.time(),&lt;br&gt;
        })&lt;/p&gt;

&lt;p&gt;@app.websocket("/stream")&lt;br&gt;
async def stream(websocket: WebSocket):&lt;br&gt;
    await websocket.accept()&lt;br&gt;
    send_lock = asyncio.Lock()&lt;br&gt;
    heartbeat_task = asyncio.create_task(&lt;br&gt;
        application_heartbeat(websocket, send_lock)&lt;br&gt;
    )&lt;br&gt;
    try:&lt;br&gt;
        while True:&lt;br&gt;
            prompt = await websocket.receive_text()&lt;br&gt;
            async for token in stream_model_async(prompt):&lt;br&gt;
                await send_json_safe(websocket, send_lock, {&lt;br&gt;
                    "type": "token", "payload": token,&lt;br&gt;
                })&lt;br&gt;
            await send_json_safe(websocket, send_lock, {&lt;br&gt;
                "type": "complete"&lt;br&gt;
            })&lt;br&gt;
    except WebSocketDisconnect:&lt;br&gt;
        pass&lt;br&gt;
    finally:&lt;br&gt;
        heartbeat_task.cancel()&lt;br&gt;
        await asyncio.gather(heartbeat_task, return_exceptions=True)&lt;br&gt;
For high-concurrency systems with multiple producers, use a bounded &lt;code&gt;asyncio.Queue(maxsize=128)&lt;/code&gt; with a single writer task. This gives explicit backpressure instead of unbounded buffering. FastAPI's WebSocket API raises &lt;code&gt;WebSocketDisconnect&lt;/code&gt; when a receive operation detects the closed connection. (&lt;a href="https://fastapi.tiangolo.com/advanced/websockets/" rel="noopener noreferrer"&gt;FastAPI&lt;/a&gt;) Use that event to cancel upstream generation and log structured metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Not to Deploy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do not increase timeouts to hide event-loop starvation
&lt;/h3&gt;

&lt;p&gt;If the event loop is blocked for 45 seconds and you increase &lt;code&gt;--ws-ping-timeout&lt;/code&gt; to 60, you have hidden the defect, not fixed it. A timeout should represent your operating policy, not camouflage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do not send JSON heartbeats and call them Ping frames
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;{"type": "ping"}&lt;/code&gt; is an application data message. Native WebSocket Ping/Pong uses protocol control frames handled by Uvicorn's transport layer. Application heartbeats solve a different problem: session liveness, UI status, and keeping intermediaries active.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do not conflate session lifetime with generation lifetime
&lt;/h3&gt;

&lt;p&gt;A WebSocket can live for hours. That does not mean one LLM request should. Add a separate generation deadline:&lt;br&gt;
async with asyncio.timeout(90):&lt;br&gt;
    async for token in stream_model_async(prompt):&lt;br&gt;
        ...&lt;br&gt;
This lets you fail one generation cleanly while keeping the user's session alive. For governance frameworks around production AI deadlines and evidence, see &lt;a href="https://subodhkc.com/blog/seven-layers-ai-compliance-nist-iso-soc2" rel="noopener noreferrer"&gt;seven layers of AI compliance&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagnostic Decision Tree
&lt;/h2&gt;

&lt;p&gt;Start: WebSocket closed with 1006&lt;br&gt;
  |&lt;br&gt;
  +-- Check server logs for "keepalive ping timeout"&lt;br&gt;
  |     |&lt;br&gt;
  |     +-- YES: Transport latency or event-loop lag&lt;br&gt;
  |     |     +-- Is LLM call async? NO -&amp;gt; Fix: use async SDK or to_thread&lt;br&gt;
  |     |     +-- Is LLM call async? YES -&amp;gt; Increase ws-ping-timeout&lt;br&gt;
  |     |           (only after measuring event-loop lag)&lt;br&gt;
  |&lt;br&gt;
  |     +-- NO: Check disconnect timing pattern&lt;br&gt;
  |           |&lt;br&gt;
  |           +-- Consistent ~60s -&amp;gt; Check proxy_read_timeout,&lt;br&gt;
  |           |                     ALB idle timeout&lt;br&gt;
  |           |&lt;br&gt;
  |           +-- Only during sync calls -&amp;gt; Event-loop blocking&lt;br&gt;
  |           |&lt;br&gt;
  |           +-- Random -&amp;gt; Network, client, process restart&lt;br&gt;
  |           |&lt;br&gt;
  |           +-- Under concurrency -&amp;gt; CPU saturation, backpressure&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Capture the exact client close code and timestamp&lt;/li&gt;
&lt;li&gt;Check server-side disconnect or error logs for ping timeout&lt;/li&gt;
&lt;li&gt;Search async handlers for &lt;code&gt;time.sleep()&lt;/code&gt;, &lt;code&gt;requests.get()&lt;/code&gt;, or sync SDK calls&lt;/li&gt;
&lt;li&gt;Measure event-loop lag with a monitoring task (see below)&lt;/li&gt;
&lt;li&gt;Verify Uvicorn &lt;code&gt;--ws-ping-interval&lt;/code&gt; and &lt;code&gt;--ws-ping-timeout&lt;/code&gt; values&lt;/li&gt;
&lt;li&gt;Confirm &lt;code&gt;--timeout-keep-alive&lt;/code&gt; is not being changed for WebSocket issues&lt;/li&gt;
&lt;li&gt;Inspect NGINX &lt;code&gt;proxy_read_timeout&lt;/code&gt; and ALB idle timeout&lt;/li&gt;
&lt;li&gt;Test slow generation deliberately: 5s, 15s, 30s, 60s, 120s with async calls&lt;/li&gt;
&lt;li&gt;Test under production-like concurrency&lt;/li&gt;
&lt;li&gt;Test client reconnection and process restarts&lt;/li&gt;
&lt;li&gt;Confirm generation cancellation fires on client disconnect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Event-loop lag monitor for production:&lt;br&gt;
async def monitor_event_loop_lag(interval=0.5, warning_threshold=0.25):&lt;br&gt;
    loop = asyncio.get_running_loop()&lt;br&gt;
    expected = loop.time() + interval&lt;br&gt;
    while True:&lt;br&gt;
        await asyncio.sleep(interval)&lt;br&gt;
        now = loop.time()&lt;br&gt;
        lag = now - expected&lt;br&gt;
        if lag &amp;gt;= warning_threshold:&lt;br&gt;
            logger.warning("event_loop_lag_seconds=%.3f", lag)&lt;br&gt;
        expected = now + interval&lt;br&gt;
If a blocking call freezes the event loop, this task freezes too. When scheduling resumes, the delay becomes observable. In production, export this as a metric rather than relying on logs alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Monitor for a Production LLM Streaming Service
&lt;/h2&gt;

&lt;p&gt;MetricWhy It MattersAbnormal WebSocket closure rate (1006)Track separately from normal 1000 closuresEvent-loop lag (p95, p99)Earliest signal of event-loop starvationTime to first model eventDistinguishes transport health from generation healthMaximum inter-event gapDetects when a stream goes silent unexpectedlyPing timeout countTransport health metricAbandoned generation rateCompute cost after client disconnectOutbound queue depthEarliest backpressure signalReconnection rateExposes transport problems before users complain&lt;br&gt;
A WebSocket service can remain technically up while a significant portion of long-running sessions are unusable. API uptime of 99.9% does not capture that. For production AI operations and deployment patterns, see &lt;a href="https://subodhkc.com/blog/12-production-readiness-checks-for-ai-pilots" rel="noopener noreferrer"&gt;12 production readiness checks for AI pilots&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Liveness Is Not Progress
&lt;/h2&gt;

&lt;p&gt;A heartbeat proves the session is alive. It does not prove the model is making progress. A mature streaming system tracks three separate timers:&lt;br&gt;
Transport liveness&lt;br&gt;
     |&lt;br&gt;
     +-- Is the WebSocket alive? (ping/pong, proxy idle)&lt;/p&gt;

&lt;p&gt;Generation liveness&lt;br&gt;
     |&lt;br&gt;
     +-- Has upstream produced meaningful activity? (TTFT, inter-event gap)&lt;/p&gt;

&lt;p&gt;Application deadline&lt;br&gt;
     |&lt;br&gt;
     +-- How long are we willing to keep this request alive? (product policy)&lt;br&gt;
Keepalive can turn a failed request into a connection that remains successfully stuck forever. Separate the timers. Cancel or account for orphaned model generations when the client disappears. At enterprise scale, uncancelled generations become spend.&lt;/p&gt;

&lt;p&gt;If you are deploying real-time LLM streaming and need an independent systems advisor to audit your WebSocket architecture, &lt;a href="https://subodhkc.com/advisory" rel="noopener noreferrer"&gt;schedule a strategic evaluation&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://subodhkc.com/blog/fastapi-websocket-1006-llm-streaming-disconnect" rel="noopener noreferrer"&gt;subodhkc.com&lt;/a&gt;. Learn more about the &lt;a href="https://haiec.com" rel="noopener noreferrer"&gt;HAIEC AI Governance Platform&lt;/a&gt;. Explore &lt;a href="https://kestrelvoice.com" rel="noopener noreferrer"&gt;KestrelVoice AI Voice Operations&lt;/a&gt;. Follow for more on AI governance, enterprise architecture, and compliance engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>fastapiwebsocketclos</category>
      <category>preventfastapistream</category>
      <category>fastapiwebsocketping</category>
      <category>asynciowebsocketkeep</category>
    </item>
    <item>
      <title>vLLM CUDA OOM at Startup? Two Bugs, Two Opposite Fixes</title>
      <dc:creator>Subodh Kc</dc:creator>
      <pubDate>Thu, 24 Sep 2026 15:17:33 +0000</pubDate>
      <link>https://dev.to/subodhkc/vllm-cuda-oom-at-startup-two-bugs-two-opposite-fixes-3g44</link>
      <guid>https://dev.to/subodhkc/vllm-cuda-oom-at-startup-two-bugs-two-opposite-fixes-3g44</guid>
      <description>&lt;h2&gt;
  
  
  Two Different OOM Errors, Two Different Fixes
&lt;/h2&gt;

&lt;p&gt;You downloaded the model. The weights fit on the GPU. vLLM starts loading. Then, before the first prompt reaches the server, the engine dies.&lt;/p&gt;

&lt;p&gt;Depending on your vLLM version and where initialization failed, the useful part of the traceback may look like this:&lt;br&gt;
ValueError: No available memory for the cache blocks.&lt;br&gt;
Try increasing &lt;code&gt;gpu_memory_utilization&lt;/code&gt; when initializing the engine.&lt;br&gt;
Or you may see the more familiar:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;torch.OutOfMemoryError: CUDA out of memory&lt;/code&gt;These errors look similar in logs. They are not fixed the same way. If you blindly lower &lt;code&gt;gpu_memory_utilization&lt;/code&gt; from 0.90 to 0.80, you may fix a CUDA OOM while making a KV cache capacity error worse. If you blindly raise it, you can do the opposite. The right fix depends on which part of vLLM's startup memory calculation failed.&lt;/p&gt;

&lt;p&gt;Current vLLM code explicitly recommends increasing &lt;code&gt;gpu_memory_utilization&lt;/code&gt; for KV cache errors and either increasing utilization or decreasing &lt;code&gt;max_model_len&lt;/code&gt; when the requested sequence length will not fit. (&lt;a href="https://github.com/vllm-project/vllm/blob/main/vllm/v1/core/kv_cache_utils.py" rel="noopener noreferrer"&gt;vLLM GitHub&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This article walks through that calculation, the Docker-specific checks worth doing, and the vLLM settings that actually matter for production deployment. For broader production AI architecture patterns, see &lt;a href="https://subodhkc.com/secure-enterprise-rag-architecture" rel="noopener noreferrer"&gt;secure enterprise RAG architecture&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Widely Copied Fix Is Partly Wrong
&lt;/h2&gt;

&lt;p&gt;A common Stack Overflow recommendation looks like this:&lt;br&gt;
LLM(&lt;br&gt;
    model=model_id,&lt;br&gt;
    gpu_memory_utilization=0.80,&lt;br&gt;
    block_size=8,&lt;br&gt;
    swap_space=4,&lt;br&gt;
    enforce_eager=True,&lt;br&gt;
    disable_custom_all_reduce=True&lt;br&gt;
)&lt;br&gt;
Several parts of that configuration deserve correction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;gpu_memory_utilization=0.80&lt;/strong&gt; can help if vLLM is attempting to use too much VRAM and CUDA itself runs out during profiling. It is not a universal fix. If vLLM is telling you the KV cache has no available memory, lowering utilization gives vLLM an even smaller memory budget. Current vLLM documentation lists the default as 0.92, not 0.90 as older tutorials claim. (&lt;a href="https://docs.vllm.ai/en/latest/api/vllm/config/cache/" rel="noopener noreferrer"&gt;vLLM Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;block_size=8&lt;/strong&gt; is not a generic OOM remedy. Current vLLM documentation does not define a universal static block-size default. The value is resolved according to platform and cache configuration. vLLM sizes the KV cache primarily from its available memory budget. Reducing block granularity does not reduce total KV-cache memory. (&lt;a href="https://docs.vllm.ai/en/latest/api/vllm/config/cache/" rel="noopener noreferrer"&gt;vLLM Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;swap_space=4&lt;/strong&gt; is deprecated and ignored in current vLLM's &lt;code&gt;LLM&lt;/code&gt; constructor. Modern vLLM exposes separate mechanisms for model-weight CPU offloading and KV-cache offloading instead. (&lt;a href="https://docs.vllm.ai/en/stable/api/vllm/" rel="noopener noreferrer"&gt;vLLM Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;disable_custom_all_reduce=True&lt;/strong&gt; disables vLLM's custom all-reduce kernel and falls back to NCCL. That can be useful for compatibility issues, but current documentation does not describe it as an OOM-control mechanism. (&lt;a href="https://docs.vllm.ai/en/stable/configuration/engine_args/" rel="noopener noreferrer"&gt;vLLM Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Do not tune five unrelated flags because one Stack Overflow answer happened to start successfully with them. Diagnose which memory boundary failed first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What vLLM Does With Your GPU During Startup
&lt;/h2&gt;

&lt;p&gt;Stop thinking about VRAM as a single number. If &lt;code&gt;nvidia-smi&lt;/code&gt; says 24564 MiB total, that does not mean vLLM has 24 GB available for the KV cache. The GPU must accommodate several consumers:&lt;br&gt;
+-----------------------------------+&lt;br&gt;
|         Total GPU VRAM            |&lt;br&gt;
|  (e.g. 24564 MiB = ~24 GB)        |&lt;br&gt;
+-----------------------------------+&lt;br&gt;
|  Model weights                    |&lt;br&gt;
|  (e.g. 8B params x 2 bytes = 16GB)|&lt;br&gt;
+-----------------------------------+&lt;br&gt;
|  Runtime / non-PyTorch allocs     |&lt;br&gt;
+-----------------------------------+&lt;br&gt;
|  Peak activation memory           |&lt;br&gt;
+-----------------------------------+&lt;br&gt;
|  CUDA graph memory                |&lt;br&gt;
+-----------------------------------+&lt;br&gt;
|  KV cache                         |&lt;br&gt;
|  (what remains after profiling)   |&lt;br&gt;
+-----------------------------------+&lt;br&gt;
|  Safety / unused headroom         |&lt;br&gt;
+-----------------------------------+&lt;br&gt;
vLLM's GPU worker performs a profiling run because it cannot know all of those values from model parameter count alone. The engine loads the model, executes a profiling pass with dummy inputs to estimate non-KV peak memory, then determines how much remains for the KV cache. Recent vLLM versions also account for estimated CUDA graph memory in this calculation. (&lt;a href="https://docs.vllm.ai/en/latest/api/vllm/v1/worker/gpu_worker/" rel="noopener noreferrer"&gt;vLLM Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The startup profiling sequence looks like this:&lt;br&gt;
vLLM Startup Memory Profiling&lt;br&gt;
         |&lt;br&gt;
         v&lt;br&gt;
  Load model weights onto GPU&lt;br&gt;
         |&lt;br&gt;
         v&lt;br&gt;
  Run profiling pass with dummy inputs&lt;br&gt;
  (measures peak activation memory)&lt;br&gt;
         |&lt;br&gt;
         v&lt;br&gt;
  Estimate CUDA graph memory&lt;br&gt;
  (v0.21.0+ accounts for this)&lt;br&gt;
         |&lt;br&gt;
         v&lt;br&gt;
  Calculate remaining budget:&lt;br&gt;
  gpu_memory_utilization x total VRAM&lt;br&gt;
    - weights - activations - graphs&lt;br&gt;
    = available KV cache memory&lt;br&gt;
         |&lt;br&gt;
         v&lt;br&gt;
  Allocate KV cache blocks&lt;br&gt;
         |&lt;br&gt;
    +----+----+&lt;br&gt;
    |         |&lt;br&gt;
   OK        FAIL&lt;br&gt;
    |         |&lt;br&gt;
    v         v&lt;br&gt;
 Server     "No available memory&lt;br&gt;
 starts     for the cache blocks"&lt;br&gt;
Conceptually, the calculation is: requested vLLM memory budget minus model, activation, runtime, and CUDA graph memory equals available KV-cache memory. This mental model is more useful than treating GPU memory as a single pool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Diagnostic Table
&lt;/h2&gt;

&lt;p&gt;The distinction between failure patterns will save more time than any magic 0.80 setting:&lt;br&gt;
Error PatternWhat FailedFirst Actions&lt;code&gt;torch.OutOfMemoryError: CUDA out of memory&lt;/code&gt;Physical CUDA allocationFree GPU memory, lower utilization, reduce context/batch, test eager mode&lt;code&gt;No available memory for the cache blocks&lt;/code&gt;Requested vLLM budget leaves no KV capacityIncrease utilization if real headroom exists; otherwise reduce model/memory needs&lt;code&gt;KV cache needed ... larger than available&lt;/code&gt;&lt;code&gt;max_model_len&lt;/code&gt; exceeds KV capacityReduce &lt;code&gt;max_model_len&lt;/code&gt;; increase available KV memory where safeOOM during CUDA graph captureGraph/runtime overheadTry &lt;code&gt;--enforce-eager&lt;/code&gt; or smaller graph configuration&lt;code&gt;Cannot re-initialize CUDA in forked subprocess&lt;/code&gt;CUDA multiprocessing initializationFix process model; do not treat as ordinary OOMContainer cannot see GPUNVIDIA runtime configurationValidate &lt;code&gt;--gpus&lt;/code&gt;, &lt;code&gt;NVIDIA_VISIBLE_DEVICES&lt;/code&gt;, driver&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Failure Paths
&lt;/h2&gt;

&lt;p&gt;The most important distinction in vLLM startup OOM is which memory boundary failed. These two failure paths require opposite fixes in some cases:&lt;br&gt;
Failure A: CUDA allocation OOM          Failure B: KV cache capacity error&lt;br&gt;
         |                                       |&lt;br&gt;
         v                                       |&lt;br&gt;
  "torch.OutOfMemoryError:              "No available memory for&lt;br&gt;
   CUDA out of memory"                  the cache blocks"&lt;br&gt;
         |                                       |&lt;br&gt;
         v                                       v&lt;br&gt;
  GPU ran out of physical              vLLM budget too small for&lt;br&gt;
  memory during allocation             required KV cache capacity&lt;br&gt;
         |                                       |&lt;br&gt;
         v                                       v&lt;br&gt;
  FIX: Lower utilization               FIX: Increase utilization&lt;br&gt;
  (give vLLM smaller target)          (if real VRAM is available)&lt;br&gt;
  OR: Free GPU from other procs        OR: Reduce max_model_len&lt;br&gt;
  OR: Reduce max_model_len             OR: Use FP8 KV cache&lt;br&gt;
  OR: Disable CUDA graphs              OR: Tensor parallelism&lt;br&gt;
         |                                       |&lt;br&gt;
         v                                       v&lt;br&gt;
  More headroom = safer               More budget = more cache&lt;br&gt;
  but less cache capacity             but less safety headroom&lt;br&gt;
Lowering &lt;code&gt;gpu_memory_utilization&lt;/code&gt; helps Failure A but worsens Failure B. Raising it helps Failure B but worsens Failure A. Read the error message before touching any setting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Verify Docker GPU Access
&lt;/h2&gt;

&lt;p&gt;A Docker container does not automatically give vLLM a private pool of VRAM. The NVIDIA Container Toolkit's &lt;code&gt;--gpus&lt;/code&gt; and &lt;code&gt;NVIDIA_VISIBLE_DEVICES&lt;/code&gt; controls determine which GPUs are visible. (&lt;a href="https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/docker-specialized.html" rel="noopener noreferrer"&gt;NVIDIA Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Start on the host:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nvidia-smi&lt;/code&gt;Then check inside your vLLM container:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker exec -it &amp;amp;lt;vllm-container&amp;amp;gt; nvidia-smi&lt;/code&gt;If the server has not started yet, validate GPU access independently:&lt;br&gt;
docker run --rm --gpus all \&lt;br&gt;
  nvidia/cuda:12.8.0-base-ubuntu22.04 \&lt;br&gt;
  nvidia-smi&lt;br&gt;
What matters is the memory picture immediately before vLLM starts. If another Python process, notebook, or neighboring container owns several gigabytes of VRAM, changing vLLM's internal cache settings does not reclaim that memory. For production container deployment patterns, see &lt;a href="https://subodhkc.com/build-internal-ai-applications-streamlit-rag-mcp" rel="noopener noreferrer"&gt;building internal AI applications with Streamlit, RAG, and MCP&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Confirm Model Weights Fit
&lt;/h2&gt;

&lt;p&gt;Paged KV caching cannot rescue a model whose weights consume all available VRAM. A rough unquantized weight calculation: parameter count times bytes per parameter. An 8-billion-parameter model in BF16 is approximately 16 GB of weights. That is before runtime memory, activations, CUDA graphs, and KV cache.&lt;/p&gt;

&lt;p&gt;If model weights leave almost no GPU headroom, tuning &lt;code&gt;block_size&lt;/code&gt; is not the fix. Your options: use a smaller model, use an appropriate quantized model, use tensor parallelism across multiple GPUs, or offload model weights to CPU. vLLM's memory-conservation guide recommends quantization and tensor parallelism for this class of problem. (&lt;a href="https://docs.vllm.ai/en/latest/configuration/conserving_memory/" rel="noopener noreferrer"&gt;vLLM Docs&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Reduce max_model_len Before Chasing Obscure Flags
&lt;/h2&gt;

&lt;p&gt;Modern open-weight models advertise context windows of tens or hundreds of thousands of tokens. That does not mean your deployment needs to reserve enough KV-cache capacity to serve that entire context. If your application only expects 4K prompts, launching with a much larger maximum imposes memory requirements you do not need.&lt;br&gt;
vllm serve "$MODEL" \&lt;br&gt;
  --max-model-len 4096 \&lt;br&gt;
  --max-num-seqs 1&lt;br&gt;
vLLM explicitly recommends reducing &lt;code&gt;max_model_len&lt;/code&gt; and &lt;code&gt;max_num_seqs&lt;/code&gt; to reduce memory consumption. (&lt;a href="https://docs.vllm.ai/en/latest/configuration/conserving_memory/" rel="noopener noreferrer"&gt;vLLM Docs&lt;/a&gt;) The production value should come from your workload requirements, not from whichever number makes the server boot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Tune gpu_memory_utilization Based on the Error
&lt;/h2&gt;

&lt;p&gt;For a true CUDA OOM, a conservative diagnostic run:&lt;br&gt;
vllm serve "$MODEL" \&lt;br&gt;
  --gpu-memory-utilization 0.80 \&lt;br&gt;
  --max-model-len 4096 \&lt;br&gt;
  --max-num-seqs 1&lt;br&gt;
If that starts successfully, the previous configuration was operating too close to the device's usable memory boundary. You can then increase utilization gradually while monitoring startup logs.&lt;/p&gt;

&lt;p&gt;But if the result is &lt;code&gt;No available memory for the cache blocks&lt;/code&gt;, the smaller memory target left too little room for the cache. Do not keep marching utilization downward. You are shrinking vLLM's cache budget. Either reduce the requested context or, if &lt;code&gt;nvidia-smi&lt;/code&gt; confirms genuine unused VRAM, allow vLLM a larger fraction. The error text matters. Read it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Use enforce_eager for CUDA Graph Memory Issues
&lt;/h2&gt;

&lt;p&gt;vLLM uses CUDA graphs for performance, and graphs consume GPU memory. The project's memory-conservation documentation explicitly recommends &lt;code&gt;enforce_eager=True&lt;/code&gt; to disable CUDA graph capture completely. (&lt;a href="https://docs.vllm.ai/en/latest/configuration/conserving_memory/" rel="noopener noreferrer"&gt;vLLM Docs&lt;/a&gt;)&lt;br&gt;
vllm serve "$MODEL" \&lt;br&gt;
  --max-model-len 4096 \&lt;br&gt;
  --gpu-memory-utilization 0.80 \&lt;br&gt;
  --enforce-eager&lt;br&gt;
If eager execution starts but the normal configuration does not, CUDA graph memory is part of the boundary you are crossing. Treat eager mode as a diagnostic and capacity lever, not an automatic production default. Disabling graphs can reduce inference performance. For runtime verification of production AI systems, see &lt;a href="https://subodhkc.com/products/llmverify" rel="noopener noreferrer"&gt;llmverify&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Consider FP8 KV Cache for KV Capacity Bottlenecks
&lt;/h2&gt;

&lt;p&gt;vLLM supports quantized KV cache formats including FP8 on supported hardware. FP8 KV-cache quantization can significantly reduce the cache memory footprint, allowing more tokens or greater concurrency. (&lt;a href="https://docs.vllm.ai/en/latest/features/quantization/quantized_kvcache/" rel="noopener noreferrer"&gt;vLLM Docs&lt;/a&gt;)&lt;br&gt;
vllm serve "$MODEL" \&lt;br&gt;
  --kv-cache-dtype fp8&lt;br&gt;
Quantization strategy and scale calibration affect accuracy. vLLM's documentation recommends dataset-based calibration when appropriate. Use KV-cache quantization because you have measured a KV-capacity bottleneck and tested model quality, not simply because an OOM appeared.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Production Tuning Order
&lt;/h2&gt;

&lt;p&gt;When vLLM will not start, change things in this order:&lt;br&gt;
PriorityCheck or ChangeWhy1&lt;code&gt;nvidia-smi&lt;/code&gt;Establish actual free VRAM2Pin vLLM versionDefaults and memory behavior change between versions3Confirm model fitsNo cache tuning fixes oversized weights4Set realistic &lt;code&gt;max_model_len&lt;/code&gt;Direct control over KV requirement5Reduce &lt;code&gt;max_num_seqs&lt;/code&gt;Lowers batch/concurrency memory6Tune &lt;code&gt;gpu_memory_utilization&lt;/code&gt; by error typeControls executor budget7Try &lt;code&gt;enforce_eager&lt;/code&gt;Tests whether CUDA graphs are part of the boundary8Quantize model or KV cacheReduces the relevant memory footprint9Tensor parallelismSpreads model across GPUs10CPU/KV offloadingTrades performance for capacityLastRandom block-size or all-reduce flagsUsually solving a different problem&lt;br&gt;
That order deliberately favors explanations over superstition.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Production Debugging Decision Tree
&lt;/h2&gt;

&lt;p&gt;vLLM fails during startup&lt;br&gt;
          |&lt;br&gt;
          v&lt;br&gt;
Is the error a real CUDA allocation OOM?&lt;br&gt;
          |&lt;br&gt;
     +----+----+&lt;br&gt;
     |         |&lt;br&gt;
    YES        NO&lt;br&gt;
     |         |&lt;br&gt;
     v         v&lt;br&gt;
Check       Does vLLM say&lt;br&gt;
nvidia-smi  KV cache is too small?&lt;br&gt;
     |         |&lt;br&gt;
Free VRAM     +----+----+&lt;br&gt;
     |        YES        NO&lt;br&gt;
Reduce         |          |&lt;br&gt;
memory         v          v&lt;br&gt;
target     Lower        Inspect exact&lt;br&gt;
context    max_model_len failure stage&lt;br&gt;
batch          |&lt;br&gt;
graphs     If physical&lt;br&gt;
     |     VRAM exists,&lt;br&gt;
     |     consider higher&lt;br&gt;
     |     utilization&lt;br&gt;
     v&lt;br&gt;
Does model weight footprint fit?&lt;br&gt;
     |&lt;br&gt;
 +---+---+&lt;br&gt;
 |       |&lt;br&gt;
NO      YES&lt;br&gt;
 |       |&lt;br&gt;
 v       v&lt;br&gt;
Smaller/   Profile and&lt;br&gt;
quantized  tune workload&lt;br&gt;
model,&lt;br&gt;
TP,&lt;br&gt;
offload&lt;/p&gt;

&lt;h2&gt;
  
  
  What Not to Deploy
&lt;/h2&gt;

&lt;p&gt;Do not build production logic that catches a CUDA OOM and instantiates another vLLM engine with increasingly arbitrary settings:&lt;br&gt;
try:&lt;br&gt;
    llm = LLM(gpu_memory_utilization=0.80)&lt;br&gt;
except RuntimeError:&lt;br&gt;
    llm = LLM(gpu_memory_utilization=0.70)&lt;br&gt;
That pattern hides the distinction between OOM classes and makes deployment behavior dependent on runtime failure. A server that silently falls back to 0.70 because 0.80 failed today will behave differently tomorrow when a neighboring container frees 2 GB of VRAM. The 0.70 configuration was never validated. It was just the next number down.&lt;/p&gt;

&lt;p&gt;Use explicit configuration validated before the service receives traffic:&lt;br&gt;
import os&lt;br&gt;
from vllm import LLM&lt;/p&gt;

&lt;p&gt;MODEL_ID = os.environ["MODEL_ID"]&lt;/p&gt;

&lt;p&gt;llm = LLM(&lt;br&gt;
    model=MODEL_ID,&lt;br&gt;
    gpu_memory_utilization=float(&lt;br&gt;
        os.getenv("VLLM_GPU_MEMORY_UTILIZATION", "0.80")&lt;br&gt;
    ),&lt;br&gt;
    max_model_len=int(&lt;br&gt;
        os.getenv("VLLM_MAX_MODEL_LEN", "4096")&lt;br&gt;
    ),&lt;br&gt;
    max_num_seqs=int(&lt;br&gt;
        os.getenv("VLLM_MAX_NUM_SEQS", "1")&lt;br&gt;
    ),&lt;br&gt;
    enforce_eager=os.getenv(&lt;br&gt;
        "VLLM_ENFORCE_EAGER", "false"&lt;br&gt;
    ).lower() == "true",&lt;br&gt;
)&lt;br&gt;
The values should come from capacity testing for your model and hardware. A server should not silently decide that because 80% failed today, 70% is suddenly the correct production configuration. For governance of production AI deployments, see &lt;a href="https://subodhkc.com/how-to-secure-and-govern-ai" rel="noopener noreferrer"&gt;how to secure and govern AI systems&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Startup OOM vs Runtime KV Cache Pressure
&lt;/h2&gt;

&lt;p&gt;Do not confuse startup OOM with runtime KV-cache pressure. At runtime, concurrent requests and long contexts consume KV-cache capacity. vLLM may preempt requests and recompute when cache pressure becomes high rather than crashing. The performance-tuning guide recommends increasing available KV cache, decreasing maximum batch parameters, or distributing the model across more GPUs when preemption becomes frequent. (&lt;a href="https://docs.vllm.ai/en/latest/configuration/optimization/" rel="noopener noreferrer"&gt;vLLM Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;A server that cannot initialize at all is a different problem. The distinction matters when searching logs because both involve KV cache and memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Checklist: vLLM OOM Before the First Request
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Confirm the exact vLLM version&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;nvidia-smi&lt;/code&gt; on the host&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;nvidia-smi&lt;/code&gt; inside the container&lt;/li&gt;
&lt;li&gt;Check for other GPU processes&lt;/li&gt;
&lt;li&gt;Confirm model weights realistically fit&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;max_model_len&lt;/code&gt; to the application's real requirement&lt;/li&gt;
&lt;li&gt;Reduce &lt;code&gt;max_num_seqs&lt;/code&gt; while diagnosing&lt;/li&gt;
&lt;li&gt;Identify CUDA OOM vs KV-cache-capacity error&lt;/li&gt;
&lt;li&gt;Tune &lt;code&gt;gpu_memory_utilization&lt;/code&gt; in the correct direction&lt;/li&gt;
&lt;li&gt;Test &lt;code&gt;enforce_eager&lt;/code&gt; if graph memory is implicated&lt;/li&gt;
&lt;li&gt;Consider quantization if weights are the problem&lt;/li&gt;
&lt;li&gt;Consider FP8 KV cache if KV capacity is the problem&lt;/li&gt;
&lt;li&gt;Consider tensor parallelism for multi-GPU systems&lt;/li&gt;
&lt;li&gt;Consider CPU/KV offloading only with measured tradeoffs&lt;/li&gt;
&lt;li&gt;Load-test the final configuration after startup succeeds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one setting that does not belong at the top of that list is &lt;code&gt;block_size&lt;/code&gt;. And changing &lt;code&gt;gpu_memory_utilization&lt;/code&gt; from 0.90 to 0.80 does not instantly fix vLLM. Sometimes it does. Sometimes the correct move is exactly the opposite. The error message tells you which problem you actually have.&lt;/p&gt;

&lt;p&gt;If you are deploying self-hosted LLM infrastructure and need an independent systems advisor to audit your production readiness before traffic hits, &lt;a href="https://subodhkc.com/advisory" rel="noopener noreferrer"&gt;schedule a strategic evaluation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I fix vLLM CUDA out of memory during startup?&lt;/strong&gt; First check actual GPU usage with &lt;code&gt;nvidia-smi&lt;/code&gt;. Then identify whether CUDA itself failed an allocation or vLLM calculated insufficient KV-cache capacity. For true CUDA OOMs, reducing &lt;code&gt;gpu_memory_utilization&lt;/code&gt;, &lt;code&gt;max_model_len&lt;/code&gt;, or &lt;code&gt;max_num_seqs&lt;/code&gt; can help. For insufficient KV cache errors, reducing &lt;code&gt;max_model_len&lt;/code&gt; or increasing the vLLM memory budget when physical VRAM is available is usually more appropriate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should I set gpu_memory_utilization from 0.90 to 0.80?&lt;/strong&gt; Not automatically. Lowering it provides additional GPU headroom and may solve actual CUDA allocation OOMs. But it also reduces the memory budget available to vLLM's KV cache. If the error says the KV cache is too small, lowering utilization may make the problem worse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the default gpu_memory_utilization in vLLM?&lt;/strong&gt; It is version dependent. Many older vLLM releases documented a default of 0.90, while current vLLM documentation lists 0.92. Always check the documentation matching your installed version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does lowering block_size fix a vLLM OOM?&lt;/strong&gt; Not reliably. Current vLLM does not use one universal static block-size default, and total KV-cache capacity is primarily driven by available memory. Block size controls cache organization, not total memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does enforce_eager reduce vLLM memory?&lt;/strong&gt; It disables CUDA graph capture, which consumes additional GPU memory. This can reduce memory requirements, but the amount saved is model- and hardware-dependent and may come with a performance cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can FP8 KV cache reduce vLLM memory?&lt;/strong&gt; Yes, on supported hardware. vLLM documents FP8 KV-cache quantization as a way to significantly reduce KV-cache memory and support more tokens or concurrency. Accuracy and calibration should be tested before production use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Docker cause vLLM CUDA OOM errors?&lt;/strong&gt; Not inherently. Docker and the NVIDIA Container Toolkit control which GPUs are exposed to the container. You still need to inspect actual VRAM consumption and other processes using the same device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is PagedAttention in vLLM?&lt;/strong&gt; PagedAttention is the memory-management approach introduced with vLLM for handling the KV cache in blocks, inspired by virtual-memory paging. It reduces KV-cache waste and enables efficient sharing and batching. It cannot make a configuration fit when the model, runtime, and required KV cache exceed physical GPU capacity. (&lt;a href="https://arxiv.org/abs/2309.06180" rel="noopener noreferrer"&gt;arXiv&lt;/a&gt;)&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://subodhkc.com/blog/fix-vllm-cuda-out-of-memory-kv-cache-docker" rel="noopener noreferrer"&gt;subodhkc.com&lt;/a&gt;. Follow for more on AI governance, enterprise architecture, and compliance engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vllmcudaoutofmemory</category>
      <category>vllmoomstartup</category>
      <category>vllmkvcachememory</category>
      <category>gpumemoryutilization</category>
    </item>
    <item>
      <title>Claude Code MCP "Failed to Connect": Fix stdio, ENOENT, Timeout</title>
      <dc:creator>Subodh Kc</dc:creator>
      <pubDate>Tue, 22 Sep 2026 15:17:17 +0000</pubDate>
      <link>https://dev.to/subodhkc/claude-code-mcp-failed-to-connect-fix-stdio-enoent-timeout-2kok</link>
      <guid>https://dev.to/subodhkc/claude-code-mcp-failed-to-connect-fix-stdio-enoent-timeout-2kok</guid>
      <description>&lt;h2&gt;
  
  
  The Error Is a Status, Not a Root Cause
&lt;/h2&gt;

&lt;p&gt;You added a local MCP server to Claude Code. The configuration looks reasonable. The Python script works from Terminal. The database is running. Then:&lt;br&gt;
$ claude mcp list&lt;/p&gt;

&lt;p&gt;secure-local-database:&lt;br&gt;
  ✅ Failed to connect&lt;br&gt;
For a local MCP server using &lt;code&gt;stdio&lt;/code&gt;, Claude Code is not opening a TCP connection to your Python process. It launches the server as a subprocess and communicates through standard input and standard output. A local MCP server can fail to connect even though no network connection was ever attempted. (&lt;a href="https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/run/index.md" rel="noopener noreferrer"&gt;MCP Python SDK&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Claude Code can show MCP servers in several states: &lt;code&gt;Connected&lt;/code&gt;, &lt;code&gt;Needs authentication&lt;/code&gt;, &lt;code&gt;Failed to connect&lt;/code&gt;, &lt;code&gt;Pending approval&lt;/code&gt;, and &lt;code&gt;Rejected&lt;/code&gt;. A server showing &lt;code&gt;Failed to connect&lt;/code&gt; means Claude Code could not establish the configured MCP integration. It does not tell you which architectural layer failed. Project-scoped servers can also remain at &lt;code&gt;Pending approval&lt;/code&gt; until the workspace and server configuration are explicitly trusted. (&lt;a href="https://code.claude.com/docs/en/mcp" rel="noopener noreferrer"&gt;Claude Code Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The debugging principle: do not start at the server code. Start at the first boundary Claude Code actually attempted to cross.&lt;br&gt;
Configuration&lt;br&gt;
     |&lt;br&gt;
     v&lt;br&gt;
Trust / approval&lt;br&gt;
     |&lt;br&gt;
     v&lt;br&gt;
Process or network transport&lt;br&gt;
     |&lt;br&gt;
     v&lt;br&gt;
MCP protocol&lt;br&gt;
     |&lt;br&gt;
     v&lt;br&gt;
Tool discovery&lt;br&gt;
     |&lt;br&gt;
     v&lt;br&gt;
Tool execution&lt;br&gt;
     |&lt;br&gt;
     v&lt;br&gt;
Downstream system&lt;br&gt;
Prove those layers in order and most MCP failures stop looking mysterious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identify the Transport Before Touching Anything
&lt;/h2&gt;

&lt;p&gt;MCP supports different transport models. The two that matter for this article:&lt;br&gt;
Local stdio MCPRemote HTTP MCPClient launches a subprocessServer is already running elsewhereCommunication uses stdin/stdoutCommunication uses HTTPExecutable path mattersURL, DNS and routing matterOS permissions matterTLS, proxy and firewall matterstdout is protocol trafficHTTP body/headers carry protocol&lt;code&gt;ENOENT&lt;/code&gt; is meaningfulConnection refused/HTTP status is meaningfulLocal environment variables matterOAuth/HTTP credentials often matter&lt;br&gt;
The current MCP Python SDK describes &lt;code&gt;stdio&lt;/code&gt; as the default transport for local servers and Streamable HTTP as the deployment transport. SSE remains available for compatibility but is not recommended for new servers. (&lt;a href="https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/run/index.md" rel="noopener noreferrer"&gt;MCP Python SDK&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Before searching for &lt;code&gt;MCP connection refused&lt;/code&gt;, ask whether there is actually a network connection here. For a normal local Claude Code stdio server, the answer is no.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Class 0: The Server Is Waiting for Approval
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;claude mcp list&lt;/code&gt;. If you see &lt;code&gt;Pending approval&lt;/code&gt;, your Python server has not failed. Claude Code has not authorized that project MCP configuration to run.&lt;/p&gt;

&lt;p&gt;Project-scoped MCP servers live in the repository's &lt;code&gt;.mcp.json&lt;/code&gt;. Claude Code requires approval for those servers in interactive sessions because a repository should not be able to clone itself onto a developer's machine and silently instruct Claude Code to execute an arbitrary local process. A repository cannot simply commit its own approval and bypass an untrusted-workspace decision. (&lt;a href="https://code.claude.com/docs/en/mcp" rel="noopener noreferrer"&gt;Claude Code Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Open Claude Code interactively with &lt;code&gt;claude&lt;/code&gt;, review the workspace trust prompt, then check again. For details: &lt;code&gt;claude mcp get secure-local-database&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This behavior is not developer inconvenience. It is a supply-chain control. A project-level MCP configuration can contain a command that executes software on an employee workstation. Requiring trust before that configuration runs prevents version-controlled configuration from becoming an automatic local code-execution mechanism. An enterprise should preserve that boundary rather than training developers to bypass it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Class 1: Claude Code Cannot Find the Executable
&lt;/h2&gt;

&lt;p&gt;You can run &lt;code&gt;python3 /opt/company-mcp/server.py&lt;/code&gt; from your shell. Claude Code fails. Your interactive shell may initialize PATH modifications, pyenv, conda, uv, nvm, fnm, Volta, Homebrew, or corporate shell scripts before you type a command. The process launching your MCP server may see a different environment.&lt;/p&gt;

&lt;p&gt;Anthropic explicitly documents the &lt;code&gt;spawn ... ENOENT&lt;/code&gt; failure class when Claude Code cannot find the executable configured for a stdio MCP server, and recommends supplying the full executable path. (&lt;a href="https://code.claude.com/docs/en/mcp" rel="noopener noreferrer"&gt;Claude Code Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;On Linux or macOS: &lt;code&gt;which python3&lt;/code&gt;, &lt;code&gt;which uv&lt;/code&gt;, &lt;code&gt;which node&lt;/code&gt;. On Windows: &lt;code&gt;where.exe python&lt;/code&gt;. Then configure the resolved executable:&lt;br&gt;
{&lt;br&gt;
  "mcpServers": {&lt;br&gt;
    "secure-local-database": {&lt;br&gt;
      "type": "stdio",&lt;br&gt;
      "command": "/home/me/audit-mcp/.venv/bin/python",&lt;br&gt;
      "args": ["/home/me/audit-mcp/server.py"]&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
Do not solve PATH problems by inventing a smaller PATH. A common recommendation is &lt;code&gt;"PATH": "/usr/local/bin:/usr/bin:/bin"&lt;/code&gt;. Sometimes that works. Sometimes it removes the exact runtime you need. The better production pattern is: pinned runtime, pinned environment, absolute executable, absolute server path. This also makes the MCP deployment reproducible on another developer machine or CI runner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Class 2: OS Refuses to Run the Executable
&lt;/h2&gt;

&lt;p&gt;If you launch a Python script directly, check &lt;code&gt;ls -l /opt/company-mcp/server.py&lt;/code&gt; and make sure the file is executable with a valid interpreter line. A cleaner configuration uses the interpreter as the executable:&lt;br&gt;
{&lt;br&gt;
  "command": "/opt/company-mcp/.venv/bin/python",&lt;br&gt;
  "args": ["/opt/company-mcp/server.py"]&lt;br&gt;
}&lt;br&gt;
Check directory traversal permissions: &lt;code&gt;ls -ld /opt&lt;/code&gt; and &lt;code&gt;ls -ld /opt/company-mcp&lt;/code&gt;. A file can be readable while a parent directory remains inaccessible. This becomes particularly relevant on hardened Linux workstations, mounted enterprise volumes, WSL, containers, and corporate endpoint-management environments. A permission failure is an operating-system boundary. Changing MCP protocol settings will not fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Class 3: The Process Starts and Crashes Immediately
&lt;/h2&gt;

&lt;p&gt;Claude Code successfully launches Python. Python starts. Then something throws: &lt;code&gt;ModuleNotFoundError&lt;/code&gt;, &lt;code&gt;ImportError&lt;/code&gt;, &lt;code&gt;KeyError&lt;/code&gt;, certificate error, database authentication error. From Claude Code's perspective, the MCP server disappeared. From Python's perspective, it crashed before it ever became a usable server.&lt;/p&gt;

&lt;p&gt;The highest-value diagnostic is simple: run exactly the configured command yourself. If &lt;code&gt;.mcp.json&lt;/code&gt; says &lt;code&gt;"command": "/home/me/mcp/.venv/bin/python"&lt;/code&gt; with &lt;code&gt;"args": ["/home/me/mcp/server.py"]&lt;/code&gt;, run &lt;code&gt;/home/me/mcp/.venv/bin/python /home/me/mcp/server.py&lt;/code&gt;. A healthy stdio server run manually may appear to hang silently because it is waiting for a host to send protocol traffic over stdin. There is no port to open and no server listening banner. (&lt;a href="https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/run/index.md" rel="noopener noreferrer"&gt;MCP Python SDK&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;If your process immediately exits instead, find the startup exception first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Class 4: stdout Corrupts the stdio Protocol
&lt;/h2&gt;

&lt;p&gt;The server can appear healthy while corrupting the protocol. For a stdio MCP server, stdout is the protocol wire. The MCP transport specification reserves it for MCP messages. Local diagnostic logs should go to stderr.&lt;/p&gt;

&lt;p&gt;Use Python logging instead of print:&lt;br&gt;
import logging&lt;/p&gt;

&lt;p&gt;logging.basicConfig(level=logging.INFO)&lt;br&gt;
logger = logging.getLogger(&lt;strong&gt;name&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;logger.info("Starting database MCP server")&lt;br&gt;
The current Python SDK moves the wire to a private descriptor during serving and diverts flushed stdout to stderr, but output flushed to stdout before serving begins still lands on the wire. A dependency that prints during import can contaminate the transport without your server ever calling &lt;code&gt;print()&lt;/code&gt;. (&lt;a href="https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/run/index.md" rel="noopener noreferrer"&gt;MCP Python SDK&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Do not use &lt;code&gt;sys.stdout.flush()&lt;/code&gt; as a protocol fix. If invalid output has already been written, flushing ensures those invalid bytes reach the protocol stream. The fix is to not use stdout for non-protocol output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Class 5: Missing Environment Configuration
&lt;/h2&gt;

&lt;p&gt;Database MCP servers commonly depend on &lt;code&gt;DATABASE_URL&lt;/code&gt; or &lt;code&gt;API_TOKEN&lt;/code&gt;. Your shell may have that variable. Claude Code's MCP subprocess may not. Project &lt;code&gt;.mcp.json&lt;/code&gt; supports environment-variable expansion using &lt;code&gt;${VAR}&lt;/code&gt; and &lt;code&gt;${VAR:-default}&lt;/code&gt; in commands, arguments, environment values, URLs, and HTTP headers. (&lt;a href="https://code.claude.com/docs/en/mcp" rel="noopener noreferrer"&gt;Claude Code Docs&lt;/a&gt;)&lt;br&gt;
{&lt;br&gt;
  "mcpServers": {&lt;br&gt;
    "secure-local-database": {&lt;br&gt;
      "type": "stdio",&lt;br&gt;
      "command": "/absolute/path/to/.venv/bin/python",&lt;br&gt;
      "args": ["/absolute/path/to/server.py"],&lt;br&gt;
      "env": {&lt;br&gt;
        "DATABASE_URL": "${MCP_DATABASE_URL}"&lt;br&gt;
      }&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
Then outside version control: &lt;code&gt;export MCP_DATABASE_URL='postgresql://readonly_user:...@127.0.0.1:5432/audit'&lt;/code&gt; and run &lt;code&gt;claude&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;${MCP_DATABASE_URL}&lt;/code&gt; is not defined and has no fallback, the configuration does not fail to load. Claude Code reports a missing-variable warning and leaves the literal expression unexpanded. That can create a confusing second-order failure if the downstream library attempts to interpret the placeholder as a real connection value. Treat an unresolved secret placeholder as a configuration failure even if Claude Code technically loads the file.&lt;/p&gt;

&lt;p&gt;Do not debug secrets by printing them. Log &lt;code&gt;bool(os.getenv("DATABASE_URL"))&lt;/code&gt; instead of the value itself. A troubleshooting log can outlive the incident and turn a short connection problem into a credential exposure. The MCP security guidance consistently emphasizes reducing credential exposure and limiting server privileges. (&lt;a href="https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices" rel="noopener noreferrer"&gt;MCP Security&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Class 6: The Server Is Slow, Not Broken
&lt;/h2&gt;

&lt;p&gt;A server can be correct and still take too long to become available. Current Claude Code gives us several timing concepts that should not be confused.&lt;br&gt;
ParameterDefaultWhat It ControlsWhen to Change&lt;code&gt;MCP_TIMEOUT&lt;/code&gt;30000 (30s)Timeout for an individual MCP server startup attemptIncrease only after confirming startup correctness; investigate why startup is slow first&lt;code&gt;MCP_CONNECT_TIMEOUT_MS&lt;/code&gt;5000 (5s)Blocking connection window when non-blocking is disabled or server uses &lt;code&gt;alwaysLoad&lt;/code&gt;Increase if legitimate servers need more blocking time during startup&lt;code&gt;MCP_CONNECTION_NONBLOCKING&lt;/code&gt;1 (on)Whether startup waits for MCP servers before first querySet to &lt;code&gt;0&lt;/code&gt; to restore blocking 5-second wait&lt;code&gt;MCP_DISCOVERY_CACHE&lt;/code&gt;1 (on)Cross-process MCP discovery cache for remote serversSet to &lt;code&gt;0&lt;/code&gt; to force every server to connect at startup&lt;br&gt;
For a diagnostic test: &lt;code&gt;MCP_TIMEOUT=60000 claude&lt;/code&gt;. But increasing the timeout should not become your permanent architecture without asking why startup is slow. A healthier server does lazy initialization: process starts, MCP becomes available, tool gets invoked, expensive dependency is initialized if needed. (&lt;a href="https://code.claude.com/docs/en/env-vars" rel="noopener noreferrer"&gt;Claude Code Env Vars&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Class 7: Wrong Transport in Configuration
&lt;/h2&gt;

&lt;p&gt;This configuration is incomplete:&lt;br&gt;
{&lt;br&gt;
  "mcpServers": {&lt;br&gt;
    "company-api": {&lt;br&gt;
      "url": "&lt;a href="https://example.com/mcp" rel="noopener noreferrer"&gt;https://example.com/mcp&lt;/a&gt;"&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
Claude Code interprets entries without a &lt;code&gt;type&lt;/code&gt; as stdio configuration. A URL-only server needs an explicit HTTP type: &lt;code&gt;"type": "http"&lt;/code&gt;. Claude Code reports a configuration error when a server has a URL but no type and tells the user to specify &lt;code&gt;http&lt;/code&gt;, &lt;code&gt;sse&lt;/code&gt;, or &lt;code&gt;ws&lt;/code&gt;. (&lt;a href="https://code.claude.com/docs/en/mcp" rel="noopener noreferrer"&gt;Claude Code Docs&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  The MCP 2026-07-28 Protocol Change
&lt;/h2&gt;

&lt;p&gt;MCP through the &lt;code&gt;2025-11-25&lt;/code&gt; generation used an initialization lifecycle built around &lt;code&gt;initialize&lt;/code&gt; / &lt;code&gt;initialized&lt;/code&gt; and protocol-level sessions with &lt;code&gt;Mcp-Session-Id&lt;/code&gt;. MCP &lt;code&gt;2026-07-28&lt;/code&gt; removed that architecture. The new core protocol removed the handshake exchange and the protocol-level session. Requests are self-contained, and an optional &lt;code&gt;server/discover&lt;/code&gt; RPC can be used when a client wants capability information in advance. (&lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/" rel="noopener noreferrer"&gt;MCP Blog&lt;/a&gt;)&lt;br&gt;
2025-era mental model:&lt;br&gt;
connect -&amp;gt; initialize -&amp;gt; session -&amp;gt; requests&lt;/p&gt;

&lt;p&gt;2026 model:&lt;br&gt;
request carries protocol/client metadata&lt;br&gt;
-&amp;gt; server processes request&lt;br&gt;
The current Python SDK v2 implements the new protocol while retaining compatibility with older clients and servers. Its client can probe modern discovery and fall back to legacy initialization when speaking to an older server. (&lt;a href="https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/whats-new.md" rel="noopener noreferrer"&gt;MCP Python SDK v2&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;For enterprise platform teams, this changes deployment architecture. Modern requests can be handled without requiring a protocol-level sticky session. A request can land on different server instances behind ordinary load balancing because the protocol version, client information, and capabilities travel with the request. (&lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/" rel="noopener noreferrer"&gt;MCP Blog&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Remote MCP can fail at the gateway even when the server is healthy. The current MCP &lt;code&gt;2026-07-28&lt;/code&gt; HTTP example uses headers including &lt;code&gt;MCP-Protocol-Version&lt;/code&gt;, &lt;code&gt;Mcp-Method&lt;/code&gt;, and &lt;code&gt;Mcp-Name&lt;/code&gt;. If an intermediary strips or mishandles headers, expects obsolete session behavior, or applies the wrong authentication policy, the MCP server can be completely healthy while the client still fails. Capture the request at the server boundary and verify the properties you expect to survive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the 2026 Protocol Change Matters to Infrastructure Teams
&lt;/h2&gt;

&lt;p&gt;For an individual developer, removing the handshake may sound like protocol trivia. For an enterprise platform team, it changes deployment architecture.&lt;br&gt;
Before (2025-era):&lt;/p&gt;

&lt;p&gt;Client&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
Load Balancer&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
Sticky Server A&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
Protocol session state&lt;/p&gt;

&lt;p&gt;After (2026-07-28):&lt;/p&gt;

&lt;p&gt;Client&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
Load Balancer&lt;br&gt;
  / | \&lt;br&gt;
 A  B  C&lt;br&gt;
Application-level state can still exist. The protocol simply no longer requires its own implicit server session. For organizations standardizing MCP gateways, this makes ordinary horizontal scaling easier. It also creates a new migration concern: your proxies and gateways need to understand the traffic your new clients are actually sending.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication Failures Are Different from Connection Failures
&lt;/h2&gt;

&lt;p&gt;For remote MCP servers, &lt;code&gt;401&lt;/code&gt; and &lt;code&gt;403&lt;/code&gt; are meaningful. Claude Code currently recognizes those responses as authentication-related states and can mark a remote MCP server as needing authentication. It also supports OAuth flows for remote MCP integrations. (&lt;a href="https://code.claude.com/docs/en/mcp" rel="noopener noreferrer"&gt;Claude Code Docs&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This is different from &lt;code&gt;connection refused&lt;/code&gt; and different again from &lt;code&gt;server returned 500&lt;/code&gt;. A useful remote diagnostic path is:&lt;br&gt;
DNS resolves?&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
TCP/TLS succeeds?&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
HTTP endpoint exists?&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
Authentication succeeds?&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
MCP protocol accepted?&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
Tool discovered?&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
Tool succeeds?&lt;br&gt;
When all of that gets collapsed into "MCP handshake," teams end up adjusting protocol code to solve identity problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Database Can Be Broken While MCP Is Healthy
&lt;/h2&gt;

&lt;p&gt;The most important distinction for secure local database MCP projects: a real database connection refusal can happen at the final boundary, and that is not a local MCP transport error.&lt;br&gt;
Claude Code&lt;br&gt;
     |&lt;br&gt;
     | stdio healthy&lt;br&gt;
     v&lt;br&gt;
MCP server&lt;br&gt;
     |&lt;br&gt;
     | TCP connection fails&lt;br&gt;
     v&lt;br&gt;
PostgreSQL&lt;br&gt;
From the same environment where the MCP process runs, test PostgreSQL independently: &lt;code&gt;psql "$DATABASE_URL" -c 'select 1;'&lt;/code&gt;. Then verify host, port, database name, TLS requirements, CA/client certificates, username, password, database role, Docker network, SSH context, WSL boundary, and firewall rules. Once that works, test the MCP tool. This separation prevents a simple database listener problem from turning into a protocol rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cursor Has the Same Architectural Problem
&lt;/h2&gt;

&lt;p&gt;Cursor also supports MCP and can hit many of the same subprocess, environment, and downstream-resource failures. Current Cursor uses &lt;code&gt;agent&lt;/code&gt; as the primary CLI entry point; &lt;code&gt;cursor-agent&lt;/code&gt; remains available as a backwards-compatible alias. Current CLI releases expose MCP management through &lt;code&gt;/mcp&lt;/code&gt; commands. (&lt;a href="https://cursor.com/changelog/cli-jan-08-2026" rel="noopener noreferrer"&gt;Cursor CLI Changelog&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The important point is not that Claude Code and Cursor store every setting identically. Their local MCP failure tree is structurally similar: can host resolve command, can process start, can MCP transport work, can tool be discovered, can tool reach target. If a server works in one MCP client but not another, compare the host environment, configuration, and runtime location before rewriting the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remote Development Changes What localhost Means
&lt;/h2&gt;

&lt;p&gt;This is a classic distributed-systems trap. You configure &lt;code&gt;postgresql://127.0.0.1:5432/audit&lt;/code&gt;. It works on your laptop. Then you run your MCP server in a remote development environment. Now &lt;code&gt;127.0.0.1&lt;/code&gt; means the remote machine, not your laptop.&lt;/p&gt;

&lt;p&gt;The same confusion can appear with Cursor Remote SSH, dev containers, Codespaces-like environments, WSL, Docker, and remote agents. Before investigating MCP, draw the topology:&lt;br&gt;
Where does the AI client execute?&lt;br&gt;
Where does the MCP process execute?&lt;br&gt;
Where does the database execute?&lt;br&gt;
What machine does "localhost" refer to?&lt;br&gt;
That one diagram can save an hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secure Database MCP Architecture
&lt;/h2&gt;

&lt;p&gt;The easiest database MCP tool to expose is &lt;code&gt;query(sql)&lt;/code&gt;. It is also one of the broadest interfaces you can hand to an autonomous agent. Prefer narrower tools: &lt;code&gt;get_recent_errors(service, since, limit)&lt;/code&gt;, &lt;code&gt;get_trace(trace_id)&lt;/code&gt;, &lt;code&gt;get_failed_jobs(since, limit)&lt;/code&gt;. The tool schema becomes one security boundary.&lt;/p&gt;

&lt;p&gt;Claude Code's own current MCP documentation uses PostgreSQL through DBHub as an example and explicitly recommends a read-only database user so Claude's queries cannot modify the database. (&lt;a href="https://code.claude.com/docs/en/mcp" rel="noopener noreferrer"&gt;Claude Code Docs&lt;/a&gt;)&lt;br&gt;
Claude Code&lt;br&gt;
     |&lt;br&gt;
     v&lt;br&gt;
Approved MCP server&lt;br&gt;
     |&lt;br&gt;
     v&lt;br&gt;
Narrow tool&lt;br&gt;
     |&lt;br&gt;
     v&lt;br&gt;
Read-only DB role&lt;br&gt;
     |&lt;br&gt;
     v&lt;br&gt;
Specific schema / views&lt;br&gt;
Local stdio has a smaller remote attack surface than exposing a public HTTP listener. That does not make the server harmless. MCP's security guidance warns that local MCP servers can run with the privileges of the client process and recommends sandboxing and minimal default privileges. (&lt;a href="https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices" rel="noopener noreferrer"&gt;MCP Security&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Claude Code's permission rules apply to tools, including MCP tools, before they execute. Claude Code's built-in OS sandbox applies to Bash commands and their child processes. It does not automatically mean every arbitrary MCP process has been placed inside the same operating-system sandbox. (&lt;a href="https://code.claude.com/docs/en/sandboxing" rel="noopener noreferrer"&gt;Claude Code Sandboxing&lt;/a&gt;) Those are complementary controls. For a sensitive MCP server, launch it through an isolation mechanism appropriate to the threat model rather than assuming MCP permissions alone provide process containment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Enterprise Issue Is Larger Than One Developer Configuration
&lt;/h2&gt;

&lt;p&gt;A developer sees &lt;code&gt;Failed to connect&lt;/code&gt;. A CTO should see a new integration surface. MCP can connect AI agents to databases, source control, observability, CI/CD, cloud infrastructure, ticketing, communication systems, internal documents, and customer systems. That creates enormous productivity potential. It also means the AI tooling layer can become a new control plane across systems that were previously isolated from each other.&lt;/p&gt;

&lt;p&gt;The enterprise problem is therefore not merely how to get MCP working. It is how to make MCP operable, governable, and auditable at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  A CTO-Level MCP Control Model
&lt;/h2&gt;

&lt;p&gt;I would divide enterprise MCP governance into seven controls.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Server Provenance
&lt;/h3&gt;

&lt;p&gt;Know who supplied each server. A production inventory should record server name, publisher, repository, version, transport, deployment location, business owner, technical owner, and review status. Installing an MCP package is equivalent to adding executable integration code to the environment. Treat it that way.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Capability Scope
&lt;/h3&gt;

&lt;p&gt;Inventory not just servers but exposed tools. A server named &lt;code&gt;database&lt;/code&gt; tells you almost nothing. These do: &lt;code&gt;read_orders&lt;/code&gt;, &lt;code&gt;create_ticket&lt;/code&gt;, &lt;code&gt;deploy_service&lt;/code&gt;, &lt;code&gt;delete_branch&lt;/code&gt;, &lt;code&gt;run_sql&lt;/code&gt;. The security-relevant unit is the capability.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Identity and Authorization
&lt;/h3&gt;

&lt;p&gt;Do not reuse application superuser credentials. Prefer agent identity, then MCP-specific service account, then minimum role, then minimum schema/API scope. For remote MCP, integrate authentication with enterprise identity rather than distributing long-lived secrets where possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Environment Isolation
&lt;/h3&gt;

&lt;p&gt;Determine where MCP actually executes: developer workstation, remote host, container, enterprise gateway, or cloud worker. Then define filesystem, network, and secret boundaries accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Tool Approval Policy
&lt;/h3&gt;

&lt;p&gt;Not every tool should have the same execution policy.&lt;br&gt;
CapabilityDefault PostureRead documentationAuto-allowRead logsAuto-allow with data policyQuery read-only analyticsAuto/limitedCreate issueAllow with scopeModify repositoryReview depending on branchTrigger deploymentApprovalModify infrastructureStrong approvalWrite production databaseExceptionalDelete resourcesExplicit human approval&lt;br&gt;
Cursor's current agent controls already apply approval logic to MCP tool calls, and Claude Code similarly supports tool-level permission controls. (&lt;a href="https://cursor.com/changelog/auto-review" rel="noopener noreferrer"&gt;Cursor Auto-review&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Auditability
&lt;/h3&gt;

&lt;p&gt;For a sensitive MCP tool call, an enterprise should be able to reconstruct: who initiated the session, which agent/model was involved, which MCP server/version handled it, which tool was invoked, with which normalized parameters, which identity executed the downstream action, what resource was affected, what result was returned, and whether a human approval was required. Without that evidence, MCP may improve productivity while reducing accountability. That is a bad trade.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Lifecycle Governance
&lt;/h3&gt;

&lt;p&gt;MCP servers change. Dependencies change. Tool schemas change. Permissions expand. Teams forget old integrations. Treat MCP like any other software supply chain: approve, version, monitor, review permissions, patch, retire.&lt;br&gt;
Approve&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
Version&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
Monitor&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
Review permissions&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
Patch&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
Retire&lt;br&gt;
Claude Code itself now supports managed organizational MCP configuration, including a centrally deployed server set and allowed/denied server controls. Cursor has similarly moved toward centrally distributed Team MCPs through organization marketplaces. That is where the enterprise market is heading: MCP is moving from individual developer configuration toward governed organizational infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Connection Error Can Be a Useful Security Signal
&lt;/h2&gt;

&lt;p&gt;There is a tendency in development to treat every permission error as friction. MCP is one place where that instinct can be dangerous.&lt;/p&gt;

&lt;p&gt;If the server cannot read a sensitive directory, the right fix is not automatically &lt;code&gt;chmod -R 777&lt;/code&gt;. If the database rejects the agent, the right fix is not automatically giving it the application's production-owner credential. If the repository is waiting for MCP approval, the right fix is not automatically disabling workspace trust.&lt;/p&gt;

&lt;p&gt;The acceptance criteria should be two things: integration works, and integration still has only the authority it needs. A fix that satisfies the first and breaks the second is not a production fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Minimal MCP Server Is Your Best Diagnostic Tool
&lt;/h2&gt;

&lt;p&gt;When a database MCP server refuses to connect, temporarily remove the database. The current stable MCP Python SDK is v2. Its high-level server class is &lt;code&gt;MCPServer&lt;/code&gt;; the former v1 &lt;code&gt;FastMCP&lt;/code&gt; name was changed as part of the v2 migration. (&lt;a href="https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/index.md" rel="noopener noreferrer"&gt;MCP Python SDK&lt;/a&gt;)&lt;br&gt;
from mcp.server import MCPServer&lt;/p&gt;

&lt;p&gt;mcp = MCPServer("transport-test")&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/mcp"&gt;@mcp&lt;/a&gt;.tool()&lt;br&gt;
def health() -&amp;gt; dict[str, str]:&lt;br&gt;
    return {"status": "ok"}&lt;/p&gt;

&lt;p&gt;if &lt;strong&gt;name&lt;/strong&gt; == "&lt;strong&gt;main&lt;/strong&gt;":&lt;br&gt;
    mcp.run()&lt;br&gt;
With no transport argument, &lt;code&gt;mcp.run()&lt;/code&gt; uses stdio. Prove Claude Code can reach &lt;code&gt;health()&lt;/code&gt;. If that works, your transport is not the problem. Then add the next dependency. That turns one opaque failure into a sequence of small proofs.&lt;/p&gt;

&lt;p&gt;Use MCP Inspector before blaming Claude Code: &lt;code&gt;uv run mcp dev server.py&lt;/code&gt;. The Inspector launches the server and connects over stdio much like a real host would. (&lt;a href="https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/run/index.md" rel="noopener noreferrer"&gt;MCP Python SDK&lt;/a&gt;) If the Inspector cannot talk to the server, changing Claude Code's authentication or workspace settings is unlikely to fix the server implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Configuration Pattern
&lt;/h2&gt;

&lt;p&gt;{&lt;br&gt;
  "mcpServers": {&lt;br&gt;
    "audit-database": {&lt;br&gt;
      "type": "stdio",&lt;br&gt;
      "command": "/absolute/path/audit-mcp/.venv/bin/python",&lt;br&gt;
      "args": ["/absolute/path/audit-mcp/server.py"],&lt;br&gt;
      "env": {&lt;br&gt;
        "DATABASE_URL": "${MCP_AUDIT_DATABASE_URL}"&lt;br&gt;
      }&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
The desirable properties are not the exact filenames. They are: explicit transport, explicit runtime, explicit server path, no password committed to Git, dedicated credential, narrow database privileges, reproducible environment. Those characteristics make troubleshooting easier and reduce security ambiguity at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Not to Deploy
&lt;/h2&gt;

&lt;p&gt;Avoid configurations that accumulate years of unexplained workarounds:&lt;br&gt;
{&lt;br&gt;
  "env": {&lt;br&gt;
    "PATH": "/usr/local/bin:/usr/bin:/bin",&lt;br&gt;
    "PYTHONPATH": "/random/dependencies",&lt;br&gt;
    "DATABASE_URL": "postgresql://admin:password@prod/db",&lt;br&gt;
    "MCP_TRANSPORT_MODE": "stdio",&lt;br&gt;
    "SOME_FLAG_FROM_A_GITHUB_ISSUE": "true"&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
The danger is not only that a setting is wrong. Future engineers cannot distinguish required architecture from historical superstition. A good MCP deployment should be explainable field by field. Do not add &lt;code&gt;MCP_TRANSPORT_MODE=stdio&lt;/code&gt; unless your server actually consumes it. There is no universal MCP environment variable with that meaning. Do not add &lt;code&gt;PYTHONPATH&lt;/code&gt; unless you can explain why. A reproducible Python server should have its dependencies installed into its environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Troubleshooting Ladder
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Identify stdio vs remote HTTP&lt;/li&gt;
&lt;li&gt;Run claude mcp list&lt;/li&gt;
&lt;li&gt;Resolve Pending approval / Rejected first&lt;/li&gt;
&lt;li&gt;Confirm configuration type and scope&lt;/li&gt;
&lt;li&gt;Confirm the executable exists (which / where.exe)&lt;/li&gt;
&lt;li&gt;Use absolute runtime and server paths&lt;/li&gt;
&lt;li&gt;Run the configured command manually&lt;/li&gt;
&lt;li&gt;Confirm the process stays alive&lt;/li&gt;
&lt;li&gt;Keep non-protocol output off stdout&lt;/li&gt;
&lt;li&gt;Test a one-tool server (remove database)&lt;/li&gt;
&lt;li&gt;Test with MCP Inspector (uv run mcp dev)&lt;/li&gt;
&lt;li&gt;Check environment-variable warnings&lt;/li&gt;
&lt;li&gt;Never print secrets while debugging&lt;/li&gt;
&lt;li&gt;Check MCP_TIMEOUT only after startup correctness&lt;/li&gt;
&lt;li&gt;Confirm MCP protocol/SDK generation&lt;/li&gt;
&lt;li&gt;Inspect proxy/gateway behavior for remote MCP&lt;/li&gt;
&lt;li&gt;Test the downstream database/API separately&lt;/li&gt;
&lt;li&gt;Use a dedicated least-privilege identity&lt;/li&gt;
&lt;li&gt;Prefer narrow tools over arbitrary privileged actions&lt;/li&gt;
&lt;li&gt;Test one complete tool call from the real client
## Symptom-to-Root-Cause Mapping
SymptomStart Here&lt;code&gt;Pending approval&lt;/code&gt;Workspace trust / project MCP approval&lt;code&gt;Rejected&lt;/code&gt;MCP approval settings&lt;code&gt;spawn ... ENOENT&lt;/code&gt;Executable path / PATHOS permission deniedFile or directory permissionsProcess exits instantlyImport/config/runtime startupWorks in shell, fails in Claude CodeEnvironment, executable, cwd, secretsInspector cannot connectMCP server/transport implementationInvalid protocol datastdout contaminationSlow server eventually worksStartup initialization / timeoutMissing-variable warning.mcp.json environment expansionURL server treated incorrectlyMissing &lt;code&gt;type: "http"&lt;/code&gt;Remote 401 / 403Authentication/authorizationMCP works, DB failsDatabase/network/credential layerWorks locally, fails remotelyRuntime topology / meaning of localhost
The most useful way to think about an MCP connection failure is not &lt;code&gt;Claude Code cannot connect, change MCP settings&lt;/code&gt;. It is: did Claude trust the configuration, could it launch or reach the server, did the transport work, did the protocol work, was the tool discovered, did identity and authorization work, did the downstream system work. Once those boundaries are separated, most MCP handshake failures become ordinary engineering problems with much smaller search spaces.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For CTOs, the broader conclusion: MCP is becoming an execution bridge between AI agents and enterprise systems. Connection reliability matters, but the real architectural requirement is controlled connectivity. The right process, using the right identity, reaching the right resource, with the smallest necessary authority and enough evidence to reconstruct what happened afterward.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Broader Architectural Lesson
&lt;/h2&gt;

&lt;p&gt;Model Context Protocol is often presented as a connector standard. That description is accurate but incomplete. Once an AI agent can use MCP to cross from reasoning into databases, source repositories, cloud accounts, deployment systems, ticketing, observability, and business applications, MCP becomes part of the organization's execution architecture.&lt;/p&gt;

&lt;p&gt;That means MCP deserves the same disciplines we already apply to APIs and privileged integration services: identity, authorization, least privilege, change management, observability, supply-chain review, isolation, incident response, and auditability.&lt;/p&gt;

&lt;p&gt;The protocol makes integrations easier to create. It does not make their authority less consequential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final MCP Connection Checklist
&lt;/h2&gt;

&lt;p&gt;[ ] Identify stdio vs remote HTTP&lt;br&gt;
[ ] Run claude mcp list&lt;br&gt;
[ ] Resolve Pending approval / Rejected first&lt;br&gt;
[ ] Confirm configuration type and scope&lt;br&gt;
[ ] Confirm the executable exists&lt;br&gt;
[ ] Use an absolute runtime path where practical&lt;br&gt;
[ ] Use an absolute server path&lt;br&gt;
[ ] Run the configured command manually&lt;br&gt;
[ ] Confirm the process stays alive&lt;br&gt;
[ ] Keep non-protocol output off stdout&lt;br&gt;
[ ] Test a one-tool server&lt;br&gt;
[ ] Test with MCP Inspector&lt;br&gt;
[ ] Check environment-variable warnings&lt;br&gt;
[ ] Never print secrets while debugging&lt;br&gt;
[ ] Check MCP_TIMEOUT only after startup correctness&lt;br&gt;
[ ] Confirm MCP protocol/SDK generation&lt;br&gt;
[ ] Inspect proxy/gateway behavior for remote MCP&lt;br&gt;
[ ] Test the downstream database/API separately&lt;br&gt;
[ ] Use a dedicated least-privilege identity&lt;br&gt;
[ ] Prefer narrow tools over arbitrary privileged actions&lt;br&gt;
[ ] Test one complete tool call from the real client&lt;br&gt;
[ ] Preserve approval, audit and rollback boundaries&lt;br&gt;
If you are deploying MCP integrations and need an independent systems advisor to audit your agent-to-database control boundaries, schedule a strategic evaluation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://subodhkc.com/blog/claude-code-mcp-failed-to-connect-stdio-enoent-timeout" rel="noopener noreferrer"&gt;subodhkc.com&lt;/a&gt;. Follow for more on AI governance, enterprise architecture, and compliance engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecodemcpfailedt</category>
      <category>mcpserverfailedtocon</category>
      <category>claudemcpenoent</category>
      <category>mcpstdioserver</category>
    </item>
    <item>
      <title>What Is llms.txt? Why Every AI-Ready Website Should Publish It</title>
      <dc:creator>Subodh Kc</dc:creator>
      <pubDate>Sun, 20 Sep 2026 06:14:01 +0000</pubDate>
      <link>https://dev.to/subodhkc/what-is-llmstxt-why-every-ai-ready-website-should-publish-it-36i2</link>
      <guid>https://dev.to/subodhkc/what-is-llmstxt-why-every-ai-ready-website-should-publish-it-36i2</guid>
      <description>&lt;p&gt;The next important visitor to your website may never see your homepage.&lt;/p&gt;

&lt;p&gt;It may be an AI coding assistant working inside Windsurf or Cursor. It may be a chatbot researching vendors, an automated compliance assessment, a procurement agent, a custom RAG application, or an AI assistant retrieving information for a user.&lt;/p&gt;

&lt;p&gt;These systems do not navigate websites the way humans do. They extract information from JavaScript-heavy pages, incomplete navigation, duplicated marketing content, ambiguous links, or hundreds of URLs competing for limited context.&lt;/p&gt;

&lt;p&gt;That is the problem &lt;code&gt;llms.txt&lt;/code&gt; is designed to solve.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; is a lightweight Markdown file that gives AI systems a curated map of a website: what the site represents, which pages are authoritative, and where the most useful machine-readable content can be found.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My position:&lt;/strong&gt; Websites already publish interfaces for humans, search engines, applications, accessibility tools, and APIs. Publishing an interface for AI agents is the logical next layer.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Published: July 25, 2026 · Last verified: July 25, 2026&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is llms.txt?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; is a proposed machine-readable publishing convention introduced by Jeremy Howard in September 2024. The file is placed at the root of a domain:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://example.com/llms.txt&lt;/code&gt;It provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A concise description of the website&lt;/li&gt;
&lt;li&gt;Important context about the company, product, or project&lt;/li&gt;
&lt;li&gt;Curated links to authoritative pages&lt;/li&gt;
&lt;li&gt;Descriptions explaining what each page contains&lt;/li&gt;
&lt;li&gt;Optional links that agents can skip when context is limited&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The original proposal was created because language models increasingly rely on website information, while complex HTML, navigation, advertising, scripts, and limited context windows make websites inefficient to process. It uses Markdown because the format is readable by humans, language models, parsers, and traditional tools. &lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;[1]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A simple implementation looks like this:&lt;/p&gt;

&lt;h1&gt;
  
  
  Example Company
&lt;/h1&gt;

&lt;p&gt;&amp;gt; Example Company provides AI vendor-risk assessment and governance tools.&lt;/p&gt;

&lt;p&gt;Important context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assessments are based on publicly available evidence.&lt;/li&gt;
&lt;li&gt;Results support, but do not replace, human security and legal review.&lt;/li&gt;
&lt;li&gt;The methodology page is the authoritative source for scoring logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Core Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://example.com/product.md" rel="noopener noreferrer"&gt;Product overview&lt;/a&gt;: Capabilities, intended users, and product limitations.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/methodology.md" rel="noopener noreferrer"&gt;Assessment methodology&lt;/a&gt;: Evidence collection, scoring, and confidence methodology.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/security.md" rel="noopener noreferrer"&gt;Security&lt;/a&gt;: Security architecture and data-handling practices.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/docs.md" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;: Implementation and integration guidance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Optional
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://example.com/blog.md" rel="noopener noreferrer"&gt;Company blog&lt;/a&gt;: Educational articles and company perspectives.
This is not intended to replace the website. It is a machine-readable map of the website.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The argument for llms.txt starts with how AI systems work
&lt;/h2&gt;

&lt;p&gt;An AI product such as ChatGPT is not simply one model reading the entire internet. A modern AI information pipeline contains several separate components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A crawler or search index discovers content.&lt;/li&gt;
&lt;li&gt;A retrieval system finds possible sources.&lt;/li&gt;
&lt;li&gt;A ranking layer selects relevant pages or passages.&lt;/li&gt;
&lt;li&gt;A context-building layer prepares information for the model.&lt;/li&gt;
&lt;li&gt;The language model generates an answer based on the supplied context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The language model is probabilistic. The retrieval path does not have to be.&lt;/p&gt;

&lt;p&gt;A website owner cannot control every decision the model makes, but the owner can reduce ambiguity about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which pages exist&lt;/li&gt;
&lt;li&gt;Which pages are important&lt;/li&gt;
&lt;li&gt;Which source is canonical&lt;/li&gt;
&lt;li&gt;What each page contains&lt;/li&gt;
&lt;li&gt;Which content is optional&lt;/li&gt;
&lt;li&gt;Where clean Markdown versions can be found&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the real value of &lt;code&gt;llms.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;OpenAI's own documentation separates its search crawler, OAI-SearchBot, from GPTBot and user-triggered agents. OAI-SearchBot surfaces websites in ChatGPT Search, while GPTBot is associated with content that may be used for model development. This illustrates the distinction between the model and the systems that discover and retrieve website information. &lt;a href="https://developers.openai.com/api/docs/bots" rel="noopener noreferrer"&gt;[2]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; belongs primarily in that discovery and retrieval layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  A deterministic map for a probabilistic consumer
&lt;/h2&gt;

&lt;p&gt;The strongest way to understand &lt;code&gt;llms.txt&lt;/code&gt; is not as an SEO file. It is a deterministic content interface.&lt;/p&gt;

&lt;p&gt;Traditional websites make humans infer structure through navigation bars, dropdown menus, visual cards, footer links, buttons, page hierarchy, branding, and layout. An AI retrieval system may instead encounter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Client-side rendering&lt;/li&gt;
&lt;li&gt;Repeated navigation text&lt;/li&gt;
&lt;li&gt;Multiple versions of similar pages&lt;/li&gt;
&lt;li&gt;Weak internal links&lt;/li&gt;
&lt;li&gt;Orphan pages&lt;/li&gt;
&lt;li&gt;Tracking parameters&lt;/li&gt;
&lt;li&gt;Unclear canonical sources&lt;/li&gt;
&lt;li&gt;Large amounts of marketing text&lt;/li&gt;
&lt;li&gt;Important content hidden behind interactive components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the system cannot confidently determine what exists, it may search repeatedly, guess URLs, retrieve the wrong page, or miss the information entirely. Sites assembled by AI coding tools can carry the same opacity as a &lt;a href="https://subodhkc.com/blog/hidden-seo-risk-ai-assisted-frontend-development" rel="noopener noreferrer"&gt;hidden SEO risk in AI-assisted frontend development&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; reduces that search space. It says: here is what this website represents, here are the authoritative resources, here is what each resource contains, and here are the pages you can ignore when context is limited.&lt;/p&gt;

&lt;p&gt;That is a practical design pattern whether the consumer is ChatGPT, Claude, a coding agent, an internal copilot, or a custom application.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened in my AI Vendor Risk Assessment tool
&lt;/h2&gt;

&lt;p&gt;My support for &lt;code&gt;llms.txt&lt;/code&gt; comes from building, not from following an SEO trend.&lt;/p&gt;

&lt;p&gt;I built an AI Vendor Risk Assessment tool that analyzes company websites and collects evidence related to security, privacy, AI usage, data retention, subprocessors, compliance frameworks, model providers, governance controls, and contractual limitations.&lt;/p&gt;

&lt;p&gt;During testing, the retrieval workflow repeatedly failed to find important pages. This occurred even on some of my own websites, where I already knew the pages existed.&lt;/p&gt;

&lt;p&gt;The pages were public and accessible. A human could eventually locate them. But some were buried in navigation, weakly linked, or difficult for the scraper to identify as authoritative.&lt;/p&gt;

&lt;p&gt;I added those pages to &lt;code&gt;llms.txt&lt;/code&gt; and configured the retrieval process to consult the file. The number of missed pages and unnecessary navigation attempts dropped.&lt;/p&gt;

&lt;p&gt;That result does not depend on waiting for a search engine to adopt a new ranking rule. It demonstrates an immediate architectural use case: when an AI application is designed to consult &lt;code&gt;llms.txt&lt;/code&gt;, the file makes website discovery more deterministic.&lt;/p&gt;

&lt;p&gt;The same principle applies to vendor assessments, compliance scanners, support agents, product-research agents, documentation assistants, procurement systems, IDE assistants, and custom RAG applications. A website that provides an authoritative map is easier to consume than one that requires every system to reconstruct its structure independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The evidence: agents perform better when they are given a map
&lt;/h2&gt;

&lt;p&gt;In July 2026, Mintlify published an open-source benchmark comparing four ways of serving documentation to AI agents: HTML, plain Markdown, Markdown linking to &lt;code&gt;llms.txt&lt;/code&gt;, and Markdown with the complete &lt;code&gt;llms.txt&lt;/code&gt; file embedded.&lt;/p&gt;

&lt;p&gt;The benchmark covered 2,400 runs across 20 documentation sites using Claude Code and Codex.&lt;/p&gt;

&lt;p&gt;When agents received a link to &lt;code&gt;llms.txt&lt;/code&gt;, average failed URL requests dropped from 2.23 per task with HTML to 0.11. That represents roughly 90% fewer dead URLs, alongside fewer wasted fetches and reduced token usage.&lt;/p&gt;

&lt;p&gt;Answer accuracy remained broadly similar. The improvement was navigation efficiency: agents reached the correct information faster and with less waste. Mintlify replicated the pattern across additional models, where failed requests again fell close to zero. The benchmark and implementation were published openly. &lt;a href="https://www.mintlify.com/blog/llms-txt-agent-benchmark" rel="noopener noreferrer"&gt;[3]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The practical finding:&lt;/strong&gt; Markdown alone was not enough. The map was the valuable part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chrome is already aligning the web toward agents
&lt;/h2&gt;

&lt;p&gt;The strongest signal is not a prediction from an SEO influencer. It is Chrome incorporating agent-readiness into Lighthouse.&lt;/p&gt;

&lt;p&gt;Chrome's Agentic Browsing documentation now describes &lt;code&gt;llms.txt&lt;/code&gt; as an emerging convention that provides a machine-readable summary of a website for language models and AI agents. Chrome states that without the file, agents may spend more time crawling a website to understand its structure and primary content. Lighthouse now checks whether the file can be retrieved and whether it follows the proposed format. &lt;a href="https://developer.chrome.com/docs/lighthouse/agentic-browsing/llms-txt" rel="noopener noreferrer"&gt;[4]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This matters because Chrome is not treating agent compatibility as a theoretical marketing topic. It is beginning to operationalize it as a website-quality concern.&lt;/p&gt;

&lt;p&gt;The broader Agentic Browsing work also evaluates accessibility for agents, layout stability, machine-readable form descriptions, registered WebMCP tools, and agent interaction reliability. The direction is clear: future websites will not only be read by agents — they will increasingly be operated by them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Major developer platforms are building AI-readable documentation
&lt;/h2&gt;

&lt;p&gt;The convention is being adopted by platforms whose documentation is heavily consumed by coding agents.&lt;/p&gt;

&lt;p&gt;Vercel publishes &lt;code&gt;llms.txt&lt;/code&gt; and &lt;code&gt;llms-full.txt&lt;/code&gt;, teaches developers how to implement both, and describes &lt;code&gt;llms.txt&lt;/code&gt; as the selective navigation layer while &lt;code&gt;llms-full.txt&lt;/code&gt; provides complete documentation context. Vercel specifically recommends using its machine-readable resources with ChatGPT, Claude, Gemini, Cursor, and Windsurf. &lt;a href="https://vercel.com/academy/agent-friendly-apis/add-llms-txt" rel="noopener noreferrer"&gt;[5]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cloudflare documentation now exposes site-level &lt;code&gt;llms.txt&lt;/code&gt;, product-level &lt;code&gt;llms.txt&lt;/code&gt;, &lt;code&gt;llms-full.txt&lt;/code&gt;, Markdown versions of individual pages, content negotiation for machine consumers, and agent-specific guidance embedded into documentation pages. Cloudflare's pages explicitly direct agents away from context-heavy HTML and toward Markdown and the relevant &lt;code&gt;llms.txt&lt;/code&gt; index. &lt;a href="https://developers.cloudflare.com/llms-txt/" rel="noopener noreferrer"&gt;[6]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mintlify automatically generates both files, hosts them at root and &lt;code&gt;.well-known&lt;/code&gt; locations, and advertises them using HTTP response headers so agents can discover them without already knowing their paths. &lt;a href="https://www.mintlify.com/docs/ai/llmstxt" rel="noopener noreferrer"&gt;[7]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what first-mover adoption looks like. It starts with developer documentation, where machine consumption is obvious, and expands into other information-heavy industries.&lt;/p&gt;

&lt;h2&gt;
  
  
  llms.txt is not only for developer documentation
&lt;/h2&gt;

&lt;p&gt;Documentation is the first clear use case, not the last. The original proposal explicitly identifies possible applications across software projects, businesses, legislation, personal websites, ecommerce, education, and professional profiles. &lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;[1]&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  SaaS platforms
&lt;/h3&gt;

&lt;p&gt;An AI agent can quickly identify product capabilities, pricing, integrations, security documentation, API references, limitations, and changelogs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compliance and vendor-risk platforms
&lt;/h3&gt;

&lt;p&gt;A machine-readable map can identify assessment methodologies, framework mappings, security controls, evidence requirements, privacy policies, data-retention rules, subprocessors, and product limitations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ecommerce websites
&lt;/h3&gt;

&lt;p&gt;Agents can locate product categories, return policies, warranty information, shipping policies, product specifications, and compatibility information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Professional and personal websites
&lt;/h3&gt;

&lt;p&gt;An agent can understand professional background, areas of expertise, projects, publications, speaking topics, services, and contact information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Universities and public institutions
&lt;/h3&gt;

&lt;p&gt;AI systems can locate programs, admission requirements, courses, policies, research, and public resources.&lt;/p&gt;

&lt;p&gt;The underlying need is the same: reduce the cost and ambiguity of machine retrieval.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about sites whose llms.txt files receive no traffic?
&lt;/h2&gt;

&lt;p&gt;A large Ahrefs server-log study found that most published &lt;code&gt;llms.txt&lt;/code&gt; files received no requests during the study period. It also found that AI systems generally did not probe blindly for a missing &lt;code&gt;/llms.txt&lt;/code&gt; file. When files were fetched, agents often reached them through a link, an index, or an explicit instruction. &lt;a href="https://ahrefs.com/blog/llmstxt-study/" rel="noopener noreferrer"&gt;[8]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That finding strengthens the implementation case rather than eliminating it. The correct lesson is: publishing the file silently is incomplete. Discovery must also be designed.&lt;/p&gt;

&lt;p&gt;A sitemap that is never submitted or linked is less useful. An API that nobody knows exists receives no calls. A security policy hidden in an unlinked PDF will not guide an assessment. The same principle applies to &lt;code&gt;llms.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A complete implementation should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publish &lt;code&gt;/llms.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Publish &lt;code&gt;/llms-full.txt&lt;/code&gt; where appropriate.&lt;/li&gt;
&lt;li&gt;Link the files from documentation and relevant HTML pages.&lt;/li&gt;
&lt;li&gt;Advertise them through HTTP &lt;code&gt;Link&lt;/code&gt; headers.&lt;/li&gt;
&lt;li&gt;Provide Markdown versions of important pages.&lt;/li&gt;
&lt;li&gt;Reference the file in agent instructions and integration documentation.&lt;/li&gt;
&lt;li&gt;Configure custom agents and scrapers to check it first.&lt;/li&gt;
&lt;li&gt;Monitor server logs and downstream retrieval behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The future advantage will not go to the site that merely owns the file. It will go to the site that builds the entire machine-readable content path.&lt;/p&gt;

&lt;h2&gt;
  
  
  llms.txt versus llms-full.txt
&lt;/h2&gt;

&lt;p&gt;The easiest way to understand the distinction: &lt;code&gt;llms.txt&lt;/code&gt; is the map. &lt;code&gt;llms-full.txt&lt;/code&gt; is the compiled reference.&lt;br&gt;
FilePrimary functionBest use&lt;code&gt;llms.txt&lt;/code&gt;Lists and describes authoritative resourcesSelective discovery and navigation&lt;code&gt;llms-full.txt&lt;/code&gt;Combines page content into one fileFull-context ingestionIndividual &lt;code&gt;.md&lt;/code&gt; pagesProvides clean content for one pageTargeted retrieval&lt;code&gt;sitemap.xml&lt;/code&gt;Lists indexable website URLsSearch-engine discovery&lt;code&gt;robots.txt&lt;/code&gt;Communicates crawler access preferencesAccess governanceStructured dataDescribes entities and page meaningSearch and machine interpretationMCP serverExposes live resources and toolsDynamic agent access and actions&lt;/p&gt;

&lt;h3&gt;
  
  
  Use llms.txt when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The website has many pages.&lt;/li&gt;
&lt;li&gt;The agent should retrieve only what it needs.&lt;/li&gt;
&lt;li&gt;Context efficiency matters.&lt;/li&gt;
&lt;li&gt;Content changes frequently.&lt;/li&gt;
&lt;li&gt;Individual Markdown endpoints are available.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use llms-full.txt when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The content set is reasonably contained.&lt;/li&gt;
&lt;li&gt;A user or agent needs a complete reference.&lt;/li&gt;
&lt;li&gt;The file will be attached to an AI project.&lt;/li&gt;
&lt;li&gt;The content supports documentation, compliance, or research.&lt;/li&gt;
&lt;li&gt;One-fetch ingestion is more useful than selective retrieval.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For larger sites, provide both.&lt;/p&gt;

&lt;h2&gt;
  
  
  The official llms.txt structure
&lt;/h2&gt;

&lt;p&gt;The original proposal defines a specific order: &lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;[1]&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optional byte-order mark&lt;/li&gt;
&lt;li&gt;H1 containing the site or project name&lt;/li&gt;
&lt;li&gt;Blockquote containing a short summary&lt;/li&gt;
&lt;li&gt;Optional paragraphs or lists providing context&lt;/li&gt;
&lt;li&gt;H2 sections containing lists of links&lt;/li&gt;
&lt;li&gt;Optional &lt;code&gt;## Optional&lt;/code&gt; section for secondary resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only the H1 is required, but the descriptions and curated sections create most of the practical value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enhanced production template
&lt;/h3&gt;

&lt;h1&gt;
  
  
  Company or Project Name
&lt;/h1&gt;

&lt;p&gt;&amp;gt; A concise, factual explanation of what the company, website, or project does.&lt;/p&gt;

&lt;p&gt;Canonical domain: &lt;a href="https://example.com" rel="noopener noreferrer"&gt;https://example.com&lt;/a&gt;&lt;br&gt;
Primary language: en-US&lt;br&gt;
Last reviewed: 2026-07-25&lt;br&gt;
Content scope: Public product, technical, security, and policy information.&lt;/p&gt;

&lt;p&gt;Important context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;State what the product does.&lt;/li&gt;
&lt;li&gt;Identify the intended user.&lt;/li&gt;
&lt;li&gt;Explain which page is authoritative for methodology or product claims.&lt;/li&gt;
&lt;li&gt;State relevant limitations.&lt;/li&gt;
&lt;li&gt;Clarify whether the content is informational, contractual, technical, or legal.&lt;/li&gt;
&lt;li&gt;Avoid promotional claims that cannot be independently supported.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Company and Product
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://example.com/about.md" rel="noopener noreferrer"&gt;Company overview&lt;/a&gt;: Company identity, leadership, purpose, and operating scope.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/product.md" rel="noopener noreferrer"&gt;Product overview&lt;/a&gt;: Product capabilities, intended users, and limitations.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/use-cases.md" rel="noopener noreferrer"&gt;Use cases&lt;/a&gt;: Supported applications and implementation scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://example.com/methodology.md" rel="noopener noreferrer"&gt;Methodology&lt;/a&gt;: How evidence, analysis, and results are produced.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/scoring.md" rel="noopener noreferrer"&gt;Scoring&lt;/a&gt;: Scoring rules, confidence levels, and interpretation guidance.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/limitations.md" rel="noopener noreferrer"&gt;Limitations&lt;/a&gt;: Known exclusions and conditions affecting results.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security and Privacy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://example.com/security.md" rel="noopener noreferrer"&gt;Security&lt;/a&gt;: Security architecture and control environment.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/privacy.md" rel="noopener noreferrer"&gt;Privacy&lt;/a&gt;: Personal-data collection, use, retention, and rights.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/subprocessors.md" rel="noopener noreferrer"&gt;Subprocessors&lt;/a&gt;: Third-party service providers and their roles.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/data-retention.md" rel="noopener noreferrer"&gt;Data retention&lt;/a&gt;: Retention and deletion practices.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Documentation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://example.com/docs/getting-started.md" rel="noopener noreferrer"&gt;Getting started&lt;/a&gt;: Initial setup and onboarding.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/docs/api.md" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;: Authentication, endpoints, requests, and errors.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/docs/architecture.md" rel="noopener noreferrer"&gt;Architecture&lt;/a&gt;: System components and data flows.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/changelog.md" rel="noopener noreferrer"&gt;Changelog&lt;/a&gt;: Current releases and deprecations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Commercial and Support
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://example.com/pricing.md" rel="noopener noreferrer"&gt;Pricing&lt;/a&gt;: Current plans and included capabilities.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/support.md" rel="noopener noreferrer"&gt;Support&lt;/a&gt;: Support channels and escalation procedures.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/contact.md" rel="noopener noreferrer"&gt;Contact&lt;/a&gt;: Official company contact information.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Optional
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://example.com/blog.md" rel="noopener noreferrer"&gt;Blog&lt;/a&gt;: Educational content and company perspectives.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/news.md" rel="noopener noreferrer"&gt;News&lt;/a&gt;: Announcements and media coverage.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://example.com/case-studies.md" rel="noopener noreferrer"&gt;Case studies&lt;/a&gt;: Examples of reported customer outcomes.
## Recommended llms-full.txt structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;llms-full.txt&lt;/code&gt; has become a widely implemented companion convention, although the original proposal more specifically describes generated context files named &lt;code&gt;llms-ctx.txt&lt;/code&gt; and &lt;code&gt;llms-ctx-full.txt&lt;/code&gt;. &lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;[1]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A production-quality &lt;code&gt;llms-full.txt&lt;/code&gt; should contain cleaned, authoritative content rather than an uncontrolled dump of every page.&lt;/p&gt;

&lt;h1&gt;
  
  
  Company Name: Full AI-Readable Reference
&lt;/h1&gt;

&lt;p&gt;&amp;gt; Consolidated public documentation for AI assistants, agents, retrieval systems, and human readers.&lt;/p&gt;

&lt;p&gt;Canonical domain: &lt;a href="https://example.com" rel="noopener noreferrer"&gt;https://example.com&lt;/a&gt;&lt;br&gt;
Generated: 2026-07-25T12:00:00-05:00&lt;br&gt;
Content version: 1.0&lt;br&gt;
Language: en-US&lt;br&gt;
Source index: &lt;a href="https://example.com/llms.txt" rel="noopener noreferrer"&gt;https://example.com/llms.txt&lt;/a&gt;&lt;br&gt;
Scope: Public authoritative content only&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Company Overview&lt;/li&gt;
&lt;li&gt;Product Overview&lt;/li&gt;
&lt;li&gt;Methodology&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Privacy&lt;/li&gt;
&lt;li&gt;Compliance&lt;/li&gt;
&lt;li&gt;Technical Documentation&lt;/li&gt;
&lt;li&gt;Pricing and Support&lt;/li&gt;
&lt;li&gt;Limitations&lt;/li&gt;
&lt;li&gt;Frequently Asked Questions&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Company Overview
&lt;/h2&gt;

&lt;p&gt;Source: &lt;a href="https://example.com/about" rel="noopener noreferrer"&gt;https://example.com/about&lt;/a&gt;&lt;br&gt;
Last updated: 2026-07-20&lt;/p&gt;

&lt;p&gt;[Clean Markdown content from the authoritative company page.]&lt;/p&gt;




&lt;h2&gt;
  
  
  Product Overview
&lt;/h2&gt;

&lt;p&gt;Source: &lt;a href="https://example.com/product" rel="noopener noreferrer"&gt;https://example.com/product&lt;/a&gt;&lt;br&gt;
Last updated: 2026-07-22&lt;/p&gt;

&lt;p&gt;[Complete product description, capabilities, intended users, and exclusions.]&lt;/p&gt;




&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;p&gt;Source: &lt;a href="https://example.com/methodology" rel="noopener noreferrer"&gt;https://example.com/methodology&lt;/a&gt;&lt;br&gt;
Last updated: 2026-07-24&lt;/p&gt;

&lt;p&gt;[Complete methodology, evidence hierarchy, scoring rules, confidence levels,&lt;br&gt;
human-review requirements, and known limitations.]&lt;/p&gt;

&lt;h3&gt;
  
  
  Exclude from llms-full.txt
&lt;/h3&gt;

&lt;p&gt;Do not include: cookie banners, navigation, repeated footers, tracking parameters, search-results pages, duplicate landing pages, outdated announcements, empty category pages, private customer content, security secrets, unreviewed user-generated content, or unsupported promotional claims.&lt;/p&gt;

&lt;p&gt;The goal is not maximum volume. The goal is maximum useful signal per token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://subodhkc.com/blog/hidden-seo-risk-ai-assisted-frontend-development" rel="noopener noreferrer"&gt;hidden SEO risk in AI-assisted frontend development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://subodhkc.com/research" rel="noopener noreferrer"&gt;AI systems research&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://subodhkc.com/speaking" rel="noopener noreferrer"&gt;speaking engagements&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://subodhkc.com/research" rel="noopener noreferrer"&gt;Explore AI Systems Research&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  llms.txt should be part of a broader AI SEO and GEO stack
&lt;/h2&gt;

&lt;p&gt;Generative Engine Optimization should not be reduced to one file. A strong AI-readable website includes several coordinated layers:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Crawlability
&lt;/h3&gt;

&lt;p&gt;Important pages must return clean server responses, use canonical URLs, and remain accessible to the crawlers and agents you intend to support.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Clear entities
&lt;/h3&gt;

&lt;p&gt;The company name, product names, founder identity, categories, locations, and capabilities should remain consistent across the site and external references.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Extractable answers
&lt;/h3&gt;

&lt;p&gt;Pages should contain direct definitions, factual statements, tables, FAQs, limitations, and concise summaries that can be accurately quoted.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Original evidence
&lt;/h3&gt;

&lt;p&gt;Research, methodologies, benchmarks, frameworks, case studies, and firsthand experience give AI systems a reason to use the site as a source rather than merely summarize it.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Third-party authority
&lt;/h3&gt;

&lt;p&gt;Independent references, media coverage, directories, GitHub repositories, reviews, community discussion, and citations strengthen the broader entity record.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Structured data
&lt;/h3&gt;

&lt;p&gt;Use appropriate Schema.org markup for articles, organizations, people, software applications, products, FAQs, breadcrumbs, and services.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Machine-readable resources
&lt;/h3&gt;

&lt;p&gt;Publish &lt;code&gt;llms.txt&lt;/code&gt;, &lt;code&gt;llms-full.txt&lt;/code&gt;, Markdown page versions, OpenAPI specifications, RSS feeds, XML sitemaps, structured data, and MCP resources where applicable.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Measurement
&lt;/h3&gt;

&lt;p&gt;Monitor AI crawler requests, user-triggered agent requests, access to &lt;code&gt;.md&lt;/code&gt; pages, access to &lt;code&gt;llms.txt&lt;/code&gt; and &lt;code&gt;llms-full.txt&lt;/code&gt;, brand mentions, AI citations, retrieval accuracy, missed-page rates, failed URL requests, and downstream conversions.&lt;/p&gt;

&lt;p&gt;SEO makes content discoverable in search. GEO makes content understandable and usable inside generated answers. Agent readiness makes the website navigable and actionable by machines. &lt;code&gt;llms.txt&lt;/code&gt; sits at the intersection of all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traffic is no longer limited to search-result clicks
&lt;/h2&gt;

&lt;p&gt;The traditional journey was: search query → search result → website visit.&lt;/p&gt;

&lt;p&gt;The emerging journeys look different:&lt;br&gt;
Question in an IDE â†’ documentation retrieval â†’ product adoption&lt;/p&gt;

&lt;p&gt;Chatbot request â†’ source retrieval â†’ brand recommendation&lt;/p&gt;

&lt;p&gt;Vendor-risk assessment â†’ policy discovery â†’ procurement review&lt;/p&gt;

&lt;p&gt;AI agent â†’ product documentation â†’ API implementation&lt;/p&gt;

&lt;p&gt;Internal copilot â†’ knowledge retrieval â†’ employee action&lt;br&gt;
Some of these interactions produce website visits. Others produce brand exposure, product inclusion, technical adoption, vendor shortlisting, evidence collection, API usage, agent actions, or later human visits.&lt;/p&gt;

&lt;p&gt;Vercel already positions &lt;code&gt;llms-full.txt&lt;/code&gt; as context that users can provide to ChatGPT, Claude, Gemini, Cursor, and Windsurf. Cloudflare actively routes machine consumers toward Markdown and site-level documentation indexes. &lt;a href="https://vercel.com/docs/agent-resources" rel="noopener noreferrer"&gt;[9]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The website is no longer only a destination. It is becoming a knowledge source inside other interfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first-mover advantage
&lt;/h2&gt;

&lt;p&gt;The first-mover advantage is not simply having a file before competitors. It is gaining experience with the machine-facing layer of your website.&lt;/p&gt;

&lt;p&gt;Early adopters can begin learning which pages agents fail to discover, which descriptions improve retrieval, which file structures reduce errors, which content is frequently requested, whether agents prefer individual pages or complete context, how quickly machine-facing information becomes stale, how AI systems interpret company and product entities, how agent traffic differs from search traffic, how to secure machine-facing instructions, and how to connect static content with APIs and MCP tools.&lt;/p&gt;

&lt;p&gt;The implementation cost is low when the files are generated from an existing content source. The strategic upside is asymmetric. If adoption develops slowly, the organization still gains a structured, curated content map usable by its own applications. If adoption accelerates, the organization already understands how to publish, monitor, secure, and optimize information for agents.&lt;/p&gt;

&lt;p&gt;Chrome, Vercel, Cloudflare, Mintlify, coding agents, and custom AI applications are already moving in that direction. &lt;a href="https://developer.chrome.com/docs/lighthouse/agentic-browsing/llms-txt" rel="noopener noreferrer"&gt;[4]&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to implement llms.txt correctly
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Publish both files
&lt;/h3&gt;

&lt;p&gt;/llms.txt&lt;br&gt;
/llms-full.txt&lt;/p&gt;

&lt;h3&gt;
  
  
  Provide Markdown pages
&lt;/h3&gt;

&lt;p&gt;/about.md&lt;br&gt;
/product.md&lt;br&gt;
/security.md&lt;br&gt;
/docs/getting-started.md&lt;/p&gt;

&lt;h3&gt;
  
  
  Advertise the files through HTTP headers
&lt;/h3&gt;

&lt;p&gt;Link: &amp;lt;/llms.txt&amp;gt;; rel="llms-txt", &amp;lt;/llms-full.txt&amp;gt;; rel="llms-full-txt"&lt;br&gt;
X-Llms-Txt: /llms.txt&lt;br&gt;
Mintlify currently uses this approach to improve file discovery. &lt;a href="https://www.mintlify.com/docs/ai/llmstxt" rel="noopener noreferrer"&gt;[7]&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Link from visible pages
&lt;/h3&gt;

&lt;p&gt;Add an "AI and Agent Resources" section to documentation footers, API pages, developer resources, security pages, GitHub READMEs, and integration instructions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure your own applications to use it
&lt;/h3&gt;

&lt;p&gt;A custom retrieval workflow should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check &lt;code&gt;/llms.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Parse its sections and descriptions.&lt;/li&gt;
&lt;li&gt;Identify relevant authoritative pages.&lt;/li&gt;
&lt;li&gt;Retrieve the required Markdown content.&lt;/li&gt;
&lt;li&gt;Fall back to a sitemap or conventional crawl when needed.&lt;/li&gt;
&lt;li&gt;Record failed and successful retrieval paths.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Version-control it
&lt;/h3&gt;

&lt;p&gt;Treat &lt;code&gt;llms.txt&lt;/code&gt; as production content. Monitor for unauthorized changes, broken links, stale claims, redirects, unexpected external links, prompt-injection language, and conflicts with human-facing pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to test whether llms.txt is working
&lt;/h2&gt;

&lt;p&gt;Run a controlled comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  Baseline
&lt;/h3&gt;

&lt;p&gt;Ask an agent questions requiring information from deeply nested pages without providing &lt;code&gt;llms.txt&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Intervention
&lt;/h3&gt;

&lt;p&gt;Give the same agent access to &lt;code&gt;llms.txt&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure
&lt;/h3&gt;

&lt;p&gt;Track relevant-page discovery, failed URLs, number of pages fetched, retrieval time, input tokens, answer completeness, source accuracy, stale information, and human correction required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repeat
&lt;/h3&gt;

&lt;p&gt;Use multiple models, multiple question phrasings, and multiple runs. Generative answers vary. Your evaluation method should not depend on one prompt or one successful response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is llms.txt?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; is a Markdown file that gives AI agents and retrieval systems a curated overview of a website and links to its most important resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it llm.txt or llms.txt?
&lt;/h3&gt;

&lt;p&gt;The proposed filename is &lt;code&gt;llms.txt&lt;/code&gt;, with an "s." The singular &lt;code&gt;llm.txt&lt;/code&gt; is a common typo and search variation.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is llms-full.txt?
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;llms-full.txt&lt;/code&gt; consolidates the substantive content of multiple pages into one machine-readable file that can be supplied directly to an AI assistant or retrieval system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does llms.txt replace sitemap.xml?
&lt;/h3&gt;

&lt;p&gt;No. A sitemap broadly lists indexable URLs. &lt;code&gt;llms.txt&lt;/code&gt; curates important resources, explains what they contain, and can direct agents toward Markdown versions designed for efficient retrieval.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does llms.txt replace robots.txt?
&lt;/h3&gt;

&lt;p&gt;No. &lt;code&gt;robots.txt&lt;/code&gt; communicates access preferences. &lt;code&gt;llms.txt&lt;/code&gt; communicates context and resource structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is llms.txt useful for AI SEO and GEO?
&lt;/h3&gt;

&lt;p&gt;Yes, as part of a broader machine-readable publishing strategy. It helps agents and retrieval systems understand which content exists and where authoritative information can be found.&lt;/p&gt;

&lt;h3&gt;
  
  
  Will llms.txt help AI agents navigate a website?
&lt;/h3&gt;

&lt;p&gt;Evidence from a 2,400-run Mintlify benchmark showed that providing agents with a link to &lt;code&gt;llms.txt&lt;/code&gt; substantially reduced failed URL requests and retrieval waste. &lt;a href="https://www.mintlify.com/blog/llms-txt-agent-benchmark" rel="noopener noreferrer"&gt;[3]&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Should a small website create llms.txt?
&lt;/h3&gt;

&lt;p&gt;Yes, when the website contains important product, professional, policy, compliance, or documentation content. A small file can be maintained with minimal effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should every website create llms-full.txt?
&lt;/h3&gt;

&lt;p&gt;Not necessarily. It is most valuable when the authoritative content can be consolidated without creating an excessively large or repetitive file.&lt;/p&gt;

&lt;h3&gt;
  
  
  How often should the files be updated?
&lt;/h3&gt;

&lt;p&gt;Update them whenever important pages, product capabilities, policies, methodologies, pricing, or canonical URLs change. Automatic generation from the site's source content is preferable.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do agents discover llms.txt?
&lt;/h3&gt;

&lt;p&gt;Agents may find it through direct links, HTTP headers, documentation instructions, a user-provided URL, custom application logic, or platform integrations. Publishing the file and actively exposing it is more effective than leaving it unlinked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can llms.txt contain instructions?
&lt;/h3&gt;

&lt;p&gt;It can contain factual interpretation guidance and context. Avoid manipulative language, unsupported claims, or instructions attempting to override an agent's policies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is llms.txt only for developers?
&lt;/h3&gt;

&lt;p&gt;No. It can support ecommerce, education, professional profiles, SaaS products, compliance systems, legislation, public institutions, and any website whose content may be consumed by AI applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The web was originally built for humans. Search engines added another audience, and websites responded with &lt;code&gt;robots.txt&lt;/code&gt;, XML sitemaps, canonical tags, metadata, and structured data.&lt;/p&gt;

&lt;p&gt;AI agents are now becoming another audience. They operate through chatbots, IDEs, custom applications, retrieval systems, compliance tools, procurement workflows, and agentic browsers. They need clean content, clear authority, efficient discovery, and less ambiguity.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; gives them a map. &lt;code&gt;llms-full.txt&lt;/code&gt; gives them a consolidated reference.&lt;/p&gt;

&lt;p&gt;The value is not that one file magically changes a probabilistic model. The value is that it makes the information supplied to that model more deliberate.&lt;/p&gt;

&lt;p&gt;My own vendor-risk assessment tool found pages more reliably after I introduced a curated &lt;code&gt;llms.txt&lt;/code&gt; path. Mintlify's benchmark found that agents reached documentation with far fewer failed requests when given the same kind of map. Chrome now recognizes the convention as part of its Agentic Browsing work. Major developer platforms are actively publishing and teaching machine-readable documentation.&lt;/p&gt;

&lt;p&gt;The signals are moving in one direction. Websites are becoming interfaces for machines as well as people. The companies that begin designing that interface now will understand it before everyone else is forced to catch up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A deterministic map for probabilistic systems is not a gimmick. It is good infrastructure.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Author
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Subodh KC&lt;/strong&gt; is an AI Systems Architect and Governance Expert. Former Fortune 100 AI Strategy CTL. Founder of HAIEC — integrated AI Ethics &amp;amp; Compliance. 16+ years building production AI systems from startups to global enterprise. His work includes LLMVerify, the HAIEC compliance platform, and an AI Vendor Risk Assessment system designed to discover, analyze, and validate public evidence about AI vendors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://llmstxt.org/" rel="noopener noreferrer"&gt;The /llms.txt file — llms-txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/api/docs/bots" rel="noopener noreferrer"&gt;Overview of OpenAI Crawlers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mintlify.com/blog/llms-txt-agent-benchmark" rel="noopener noreferrer"&gt;Docs URL Benchmark: Markdown &amp;amp; llms.txt &amp;gt; HTML — Mintlify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/agentic-browsing/llms-txt" rel="noopener noreferrer"&gt;llms.txt — Lighthouse — Chrome for Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vercel.com/academy/agent-friendly-apis/add-llms-txt" rel="noopener noreferrer"&gt;Add llms.txt — Vercel Academy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/llms-txt/" rel="noopener noreferrer"&gt;Cloudflare Documentation — llms.txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mintlify.com/docs/ai/llmstxt" rel="noopener noreferrer"&gt;llms.txt — Mintlify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ahrefs.com/blog/llmstxt-study/" rel="noopener noreferrer"&gt;We Analyzed 137K Sites: 97% of llms.txt Files Never Get Read — Ahrefs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vercel.com/docs/agent-resources" rel="noopener noreferrer"&gt;Agent Resources — Vercel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/search/docs/appearance/structured-data/article" rel="noopener noreferrer"&gt;Article Schema Markup — Google Search Central&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://subodhkc.com/blog/what-is-llms-txt" rel="noopener noreferrer"&gt;subodhkc.com&lt;/a&gt;. Learn more about the &lt;a href="https://haiec.com" rel="noopener noreferrer"&gt;HAIEC AI Governance Platform&lt;/a&gt;. Follow for more on AI governance, enterprise architecture, and compliance engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>whatisllmstxt</category>
      <category>llmstxt</category>
      <category>llmsfulltxt</category>
      <category>aiseo</category>
    </item>
    <item>
      <title>How to Detect Cross-Tenant Data Leakage in MCP Servers and Multi-Tenant SaaS</title>
      <dc:creator>Subodh Kc</dc:creator>
      <pubDate>Fri, 07 Aug 2026 03:42:01 +0000</pubDate>
      <link>https://dev.to/subodhkc/how-to-detect-cross-tenant-data-leakage-in-mcp-servers-and-multi-tenant-saas-2719</link>
      <guid>https://dev.to/subodhkc/how-to-detect-cross-tenant-data-leakage-in-mcp-servers-and-multi-tenant-saas-2719</guid>
      <description>&lt;h1&gt;
  
  
  The Hidden Security Gap in Multi-Tenant MCP Servers
&lt;/h1&gt;

&lt;p&gt;When you build a multi-tenant SaaS application or an MCP (Model Context Protocol) server that serves multiple organizations, &lt;strong&gt;cross-tenant data leakage&lt;/strong&gt; is one of the most dangerous vulnerabilities you can ship. A single missing &lt;code&gt;organizationId&lt;/code&gt; filter in a database query can expose one tenant's data to another — and traditional security scanners like Snyk, Semgrep, and CodeQL don't catch these patterns.&lt;/p&gt;

&lt;p&gt;That's why I built &lt;strong&gt;mcp-tenant-isolation&lt;/strong&gt; — a static analysis scanner with 57 deterministic rules specifically designed to catch tenant isolation failures in multi-tenant codebases.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Tenant Isolation?
&lt;/h2&gt;

&lt;p&gt;Tenant isolation ensures that data belonging to one organization (tenant) is never accessible to another. In a multi-tenant SaaS app, every database query, cache read, and file access must be scoped to the current tenant's &lt;code&gt;organizationId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The most common failure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// VULNERABLE: No organizationId filter&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&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;// SECURE: Tenant-scoped query&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orgId&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;It looks obvious in isolation. But in a codebase with 100+ API routes, dozens of lib functions, and complex middleware chains, missing tenant filters are &lt;strong&gt;easy to miss in code review&lt;/strong&gt; and &lt;strong&gt;impossible for traditional SAST tools to detect&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Traditional Scanners Miss This
&lt;/h2&gt;

&lt;p&gt;Tools like Snyk and Semgrep are excellent at detecting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL injection&lt;/li&gt;
&lt;li&gt;XSS&lt;/li&gt;
&lt;li&gt;Dependency vulnerabilities&lt;/li&gt;
&lt;li&gt;Secret leakage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But they don't understand &lt;strong&gt;tenant context&lt;/strong&gt;. They don't know that &lt;code&gt;organizationId&lt;/code&gt; is the tenant boundary. They don't track which functions require tenant guards. They can't tell you that &lt;code&gt;prisma.user.findMany({ where: { role: 'admin' } })&lt;/code&gt; is missing a critical tenant filter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;mcp-tenant-isolation&lt;/strong&gt; fills this gap with 57 rules across 7 categories:&lt;/p&gt;

&lt;h3&gt;
  
  
  Rule Categories
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Rules&lt;/th&gt;
&lt;th&gt;What It Detects&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database Queries (DBQ)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Missing &lt;code&gt;organizationId&lt;/code&gt; in &lt;code&gt;findMany&lt;/code&gt;, &lt;code&gt;findUnique&lt;/code&gt;, &lt;code&gt;updateMany&lt;/code&gt;, &lt;code&gt;deleteMany&lt;/code&gt;, &lt;code&gt;groupBy&lt;/code&gt;, &lt;code&gt;aggregate&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Schema (SCH)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Missing tenant columns, missing composite indexes, missing RLS policies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;File Storage Isolation (FSI)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Unscoped S3/Blob keys, missing tenant prefix in file paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache Key Scoping (CKS)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Redis/memoization keys without tenant prefix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IDOR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Insecure direct object references — accessing resources by ID without ownership check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Logging (LOG)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Missing tenant context in structured logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP-Specific (MCP)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Tool visibility, cache prefix, session binding, credential vault isolation, prompt injection surface&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; mcp-tenant-isolation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scan your codebase
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mti scan ./src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Output
&lt;/h3&gt;

&lt;p&gt;The scanner produces a clear pass/fail verdict with detailed findings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;╔══════════════════════════════════════════╗
║  MCP Tenant Isolation Scanner v1.6.2     ║
║  Verdict: FAIL                           ║
╚══════════════════════════════════════════╝

Findings: 47 active, 12 suppressed, 3 baseline

DBQ-001  HIGH   app/api/users/route.ts:23
         Missing organizationId in prisma.user.findMany()
         Remediation: Add organizationId to the where clause:
           where: { ..., organizationId: ctx.orgId }

IDOR-003 HIGH   app/api/documents/[id]/route.ts:45
         No ownership check after findUnique by ID
         Remediation: Verify result.organizationId === ctx.orgId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CI/CD Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub Actions (Pre-built Action)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;subodhkc/mcp-tenant-isolation@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./src&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sarif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Manual npx
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tenant isolation scan&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx mcp-tenant-isolation scan ./src --format sarif --output scan.sarif&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload to GitHub Code Scanning&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github/codeql-action/upload-sarif@v3&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scan.sarif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SARIF output integrates directly with &lt;strong&gt;GitHub Advanced Security&lt;/strong&gt; — findings appear in your repo's Security tab alongside CodeQL results.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP Server Integration
&lt;/h2&gt;

&lt;p&gt;The package includes an MCP server with 4 tools that AI agents (Claude Desktop, Cursor, Cline) can use:&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;"mcpServers"&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;"mcp-tenant-isolation"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp-tenant-isolation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp"&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;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;&lt;strong&gt;Tools available:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scan&lt;/code&gt; — Run tenant isolation scan on a codebase&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rules&lt;/code&gt; — List all 57 rules with descriptions and remediation hints&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;config&lt;/code&gt; — Get/set scanner configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;version&lt;/code&gt; — Get scanner version info&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means you can ask Claude: &lt;em&gt;"Scan my src directory for tenant isolation issues"&lt;/em&gt; and get structured findings with remediation hints — directly in your chat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supported Frameworks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ORMs:&lt;/strong&gt; Prisma, Drizzle, raw SQL (Knex, pg, mysql2)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web frameworks:&lt;/strong&gt; Next.js (App Router &amp;amp; Pages), Express, Fastify&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache:&lt;/strong&gt; Redis (ioredis, redis), in-memory memoization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; S3, Vercel Blob, local filesystem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth:&lt;/strong&gt; NextAuth, custom JWT, session-based&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Output Formats
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Terminal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local development with pass/fail verdict&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JSON&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Programmatic consumption, custom dashboards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SARIF 2.1.0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GitHub Code Scanning, Azure DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI JSON&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AI agent consumption with remediation hints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Markdown&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shareable reports for PRs and team review&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Suppression and Baselines
&lt;/h2&gt;

&lt;p&gt;Not every finding is actionable immediately. The scanner supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Suppressions&lt;/strong&gt; — &lt;code&gt;.mtirc.json&lt;/code&gt; config to suppress specific findings with reasons&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Baselines&lt;/strong&gt; — Mark existing findings as known debt, only fail CI on new issues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rule packs&lt;/strong&gt; — Enable/disable rule categories per project
&lt;/li&gt;
&lt;/ul&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;"suppress"&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;span class="nl"&gt;"rule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DBQ-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app/api/public/badges/route.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"reason"&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 route, no tenant context needed"&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;h2&gt;
  
  
  Real-World Results
&lt;/h2&gt;

&lt;p&gt;I ran the scanner against a production Next.js SaaS codebase with 200+ API routes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;47 active findings&lt;/strong&gt; — including 23 missing tenant filters, 8 IDOR risks, 5 unscoped cache keys&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;12 suppressed&lt;/strong&gt; — public routes with legitimate no-tenant-context access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 baselined&lt;/strong&gt; — known debt scheduled for next sprint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scan time:&lt;/strong&gt; 4.2 seconds for 50,000+ lines of TypeScript&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why This Matters for MCP Server Developers
&lt;/h2&gt;

&lt;p&gt;If you're building MCP servers that serve multiple tenants (organizations, teams, users), tenant isolation is &lt;strong&gt;the&lt;/strong&gt; security boundary. The MCP protocol doesn't enforce tenant isolation — it's up to your implementation.&lt;/p&gt;

&lt;p&gt;The 15 MCP-specific rules check for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tool visibility&lt;/strong&gt; — Are all tools visible to all tenants? Should some be org-scoped?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache prefix&lt;/strong&gt; — Is the MCP response cache keyed by tenant?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session binding&lt;/strong&gt; — Is the MCP session bound to a specific tenant?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credential vault&lt;/strong&gt; — Are per-tenant credentials isolated in the vault?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt injection surface&lt;/strong&gt; — Does the server expose tenant context to prompt injection?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/subodhkc/mcp-tenant-isolation" rel="noopener noreferrer"&gt;subodhkc/mcp-tenant-isolation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/mcp-tenant-isolation" rel="noopener noreferrer"&gt;mcp-tenant-isolation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Landing page:&lt;/strong&gt; &lt;a href="https://www.haiec.com/mcp-tenant-isolation" rel="noopener noreferrer"&gt;haiec.com/mcp-tenant-isolation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP Registry:&lt;/strong&gt; &lt;code&gt;io.github.subodhkc/mcp-tenant-isolation&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License:&lt;/strong&gt; MIT&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you found this useful, star the repo on GitHub and share with your team. Every missing tenant filter is a potential data breach waiting to happen.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>mcp</category>
      <category>saas</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Production RAG Architecture Patterns for Hybrid Search</title>
      <dc:creator>Subodh Kc</dc:creator>
      <pubDate>Fri, 31 Jul 2026 11:40:30 +0000</pubDate>
      <link>https://dev.to/subodhkc/production-rag-architecture-patterns-for-hybrid-search-3cbi</link>
      <guid>https://dev.to/subodhkc/production-rag-architecture-patterns-for-hybrid-search-3cbi</guid>
      <description>&lt;h2&gt;
  
  
  Introduction to Production RAG Architecture Patterns
&lt;/h2&gt;

&lt;p&gt;In the evolving landscape of enterprise AI, ensuring robust governance and compliance while harnessing the power of hybrid search is crucial. The Retrieval-Augmented Generation (RAG) architecture serves as a bridge, integrating large language models with external knowledge sources, enabling organizations to deliver accurate and relevant AI-driven solutions effectively. This blog post delves into practical implementation strategies for establishing RAG architecture patterns tailored for hybrid search environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding RAG Architecture
&lt;/h2&gt;

&lt;p&gt;RAG architecture combines generative models with retrieval methods, allowing AI systems to access the latest information and provide contextually relevant responses. This synergy is particularly beneficial for enterprises looking to incorporate AI solutions while adhering to compliance regulations such as HIPAA and ISO standards.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Components of RAG Architecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval Component:&lt;/strong&gt; This part retrieves relevant documents or data from external sources, ensuring the generative model is grounded in accurate information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generation Component:&lt;/strong&gt; The generative model processes the retrieved information to produce human-like text outputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback Loop:&lt;/strong&gt; Integrating user feedback to refine the model's performance over time, ensuring relevance and accuracy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementing RAG Architecture Patterns
&lt;/h2&gt;

&lt;p&gt;To successfully implement RAG architecture patterns in your hybrid search system, consider the following steps:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Define Use Cases
&lt;/h3&gt;

&lt;p&gt;Identify specific use cases where AI can add value. For instance, legal document automation, as discussed in our post on &lt;a href="https://subodhkc.com/blog/legal-document-automation" rel="noopener noreferrer"&gt;Legal Document Automation for New York Law Firms in 2026&lt;/a&gt;, can benefit from RAG architecture by ensuring compliance while generating accurate legal documents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Establish Data Sources
&lt;/h3&gt;

&lt;p&gt;Determine the data sources necessary for retrieval. This may include internal databases, public APIs, or industry-specific knowledge bases. Ensure these sources comply with relevant regulations such as HIPAA for healthcare applications or &lt;a href="https://subodhkc.com/blog/haiec-modular-ai-governance-framework" rel="noopener noreferrer"&gt;AI governance frameworks&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Choose the Right Technology Stack
&lt;/h3&gt;

&lt;p&gt;Select the appropriate tools and frameworks to build your RAG system. Consider open-source libraries like Hugging Face’s Transformers for model implementation alongside robust retrieval systems such as Elasticsearch or Pinecone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Develop the Retrieval System
&lt;/h3&gt;

&lt;p&gt;Build a retrieval system that efficiently processes queries. For instance, use vector databases to enhance search capabilities, ensuring quick access to relevant documents that can be fed into the generative model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Implement the Generation Component
&lt;/h3&gt;

&lt;p&gt;Integrate a generative model that can interpret the retrieved data and produce accurate outputs. Train the model using domain-specific data to improve its understanding and contextual relevance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Ensure Compliance and Security
&lt;/h3&gt;

&lt;p&gt;Implement strict data governance policies to adhere to compliance requirements such as HIPAA or ISO 42001. Regularly audit the system for vulnerabilities, incorporating security measures at every stage of the architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Monitor and Optimize Performance
&lt;/h3&gt;

&lt;p&gt;Establish metrics to monitor the system's performance and implement a feedback loop. Use user analytics to refine both the retrieval and generation components, enhancing overall accuracy and relevance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Example: RAG in Legal Tech
&lt;/h2&gt;

&lt;p&gt;Consider a legal tech firm leveraging RAG architecture to automate document generation. By integrating a retrieval system that sources legal precedents, the firm ensures compliance with regulations while reducing the time necessary for legal drafting. The generative model tailors documents based on the retrieved information, ensuring accuracy and legal soundness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion and Takeaway
&lt;/h2&gt;

&lt;p&gt;Establishing a robust RAG architecture for hybrid search can significantly enhance your enterprise AI initiatives. By following the outlined steps, CTOs, CISOs, and AI program leaders can implement a compliant and secure system that improves decision-making and operational efficiency. Start by defining your use cases today and choose the right technologies to drive your AI strategy forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is RAG architecture?
&lt;/h3&gt;

&lt;p&gt;RAG architecture combines retrieval and generative models, enabling AI systems to access external information for accurate and contextually relevant outputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  How can RAG architecture improve compliance?
&lt;/h3&gt;

&lt;p&gt;By integrating real-time data retrieval with generative models, RAG architecture can ensure that outputs adhere to compliance standards, reducing legal risks.&lt;/p&gt;

&lt;h3&gt;
  
  
  What technologies are best for implementing RAG systems?
&lt;/h3&gt;

&lt;p&gt;Open-source frameworks like Hugging Face’s Transformers and retrieval systems such as Elasticsearch are popular choices for implementing RAG architectures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can RAG architecture be applied in healthcare?
&lt;/h3&gt;

&lt;p&gt;Yes, RAG architecture can enhance AI applications in healthcare by ensuring that model outputs comply with HIPAA regulations while providing accurate information.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I measure the performance of a RAG system?
&lt;/h3&gt;

&lt;p&gt;Establish metrics such as retrieval accuracy, user satisfaction, and output relevance, and implement a feedback loop for ongoing optimization.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://subodhkc.com/blog/production-rag-architecture-patterns-for-hybrid-search" rel="noopener noreferrer"&gt;subodhkc.com&lt;/a&gt;. Learn more about the &lt;a href="https://haiec.com" rel="noopener noreferrer"&gt;HAIEC AI Governance Platform&lt;/a&gt;. Follow for more on AI governance, enterprise architecture, and compliance engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>rag</category>
      <category>aigovernance</category>
      <category>aicompliance</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Legal Document Automation for New York Law Firms in 2026</title>
      <dc:creator>Subodh Kc</dc:creator>
      <pubDate>Thu, 30 Jul 2026 11:35:09 +0000</pubDate>
      <link>https://dev.to/subodhkc/legal-document-automation-for-new-york-law-firms-in-2026-3ji3</link>
      <guid>https://dev.to/subodhkc/legal-document-automation-for-new-york-law-firms-in-2026-3ji3</guid>
      <description>&lt;h1&gt;
  
  
  Legal Document Automation for New York Law Firms in 2026
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F51rlto9tnkg9drgz44gi.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F51rlto9tnkg9drgz44gi.jpeg" alt="Female lawyer reviewing legal automation documents" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Which legal document automation providers lead the New York market?
&lt;/h2&gt;

&lt;p&gt;Three platforms stand out for New York law firms and legal departments evaluating automated legal documents in 2026: LEAP Legal Software, Litify, and Legal Outsourcing 2.0. Each addresses a distinct operational profile.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LEAP Legal Software&lt;/strong&gt; integrates case management, document automation, legal accounting, billing, and AI legal tools into a single platform, purpose-built for small to medium law firms that need one system to run their practice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Litify&lt;/strong&gt; delivers a legal operations platform covering intake management, matter management, document management, time and billing, spend management, reporting, workflow automation, and AI-powered legal insights, targeting enterprise legal teams and larger firms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Legal Outsourcing 2.0&lt;/strong&gt; takes a different path entirely: technology-enabled outsourcing combining experienced US-licensed attorneys with ISO 27001 certified infrastructure to handle litigation document review, contract extraction and analysis, and data breach notification review at pricing roughly half the cost of comparable US services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The efficiency case for all three rests on the same foundation. Automated contract creation eliminates the manual drafting cycle, reduces transcription errors, and produces documents that carry a consistent clause structure across every matter. For New York firms operating under state-specific compliance requirements, that consistency is not a convenience. It is a liability control measure.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do these providers compare on services, pricing, and fit?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqfk5v5nvputpe20w9h27.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqfk5v5nvputpe20w9h27.jpeg" alt="Infographic comparing legal automation software providers" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;New York legal professionals evaluating document automation software need to match vendor capabilities to their actual workflow, not just their wish list. The table below maps each provider across the dimensions that matter most for a purchasing decision.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Services Offered&lt;/th&gt;
&lt;th&gt;Specialization&lt;/th&gt;
&lt;th&gt;Pricing Model&lt;/th&gt;
&lt;th&gt;Unique Positioning&lt;/th&gt;
&lt;th&gt;Rating&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.leaplegalsoftware.com/us/" rel="noopener noreferrer"&gt;LEAP Legal Software&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Case management, document automation, legal accounting, billing, AI legal tools&lt;/td&gt;
&lt;td&gt;Small to medium law firms, all-in-one practice management&lt;/td&gt;
&lt;td&gt;Subscription per user&lt;/td&gt;
&lt;td&gt;Single platform for full practice management with embedded AI&lt;/td&gt;
&lt;td&gt;4.6★ (244 reviews)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.litify.com/" rel="noopener noreferrer"&gt;Litify&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Intake, matter management, document management, billing, spend management, reporting, workflow automation, AI insights&lt;/td&gt;
&lt;td&gt;Enterprise legal teams, large law firms, legal departments&lt;/td&gt;
&lt;td&gt;Enterprise subscription&lt;/td&gt;
&lt;td&gt;Real-time workflow visibility with AI-driven legal analysis across complex operations&lt;/td&gt;
&lt;td&gt;4.1★ (28 reviews)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://legaloutsourcing2.com/" rel="noopener noreferrer"&gt;Legal Outsourcing 2.0&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Litigation document review, contract extraction and analysis, data breach notification review, legal innovation solutions&lt;/td&gt;
&lt;td&gt;Legal process outsourcing, litigation support, contract review&lt;/td&gt;
&lt;td&gt;All-in pricing, ~50% below US market rates&lt;/td&gt;
&lt;td&gt;ISO 27001 certified; US-licensed attorneys plus technology for high-volume, compliance-grade review&lt;/td&gt;
&lt;td&gt;5★ (1 review)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;LEAP Legal Software&lt;/strong&gt; suits firms that want to stop managing five separate tools. Its embedded AI handles document generation inside the same environment where attorneys track matters and run billing, which means no data re-entry and no context switching. For a 5–20 attorney firm in New York, that integration alone recovers meaningful time each week.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmlcx1f4q7jbmzamw29k1.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmlcx1f4q7jbmzamw29k1.jpeg" alt="Legal operations manager reviewing workflow tools" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Litify&lt;/strong&gt; is built for operations where workflow visibility matters as much as document output. Legal departments managing high case volumes across multiple practice lines benefit from its reporting and spend management modules, which give general counsel a real-time picture of where matters stand and what they cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Legal Outsourcing 2.0&lt;/strong&gt; fits a different buyer: firms that need high-volume document review or contract analysis without building internal capacity. Its ISO 27001 certification addresses the data security scrutiny that New York firms face under state bar ethics guidance on cloud and third-party data handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you implement legal document automation without losing control?
&lt;/h2&gt;

&lt;p&gt;Deployment is where most automation projects either take hold or quietly collapse. The sequence matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Onboarding phases to plan for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Template mapping (one-time):&lt;/strong&gt; During initial setup, your existing clause language, conditional logic, and formatting standards get configured into the platform. This phase typically runs one to two hours and determines the quality of every document the system produces afterward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration with existing systems:&lt;/strong&gt; Connecting your law firm automation tools to your case management system, CRM, and billing platform creates a single source of truth. When client data updates in one place, &lt;a href="https://legal.thomsonreuters.com/en/insights/articles/benefits-of-document-automation" rel="noopener noreferrer"&gt;all associated documents&lt;/a&gt; cascade automatically, eliminating the version-control problems that plague manual workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow building and testing:&lt;/strong&gt; Before going live, map the decision logic for your most common document types. Test conditional clauses against real matter scenarios, not hypothetical ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human review gates:&lt;/strong&gt; Successful automation balances AI generation with governed workflows that route documents through attorney review before delivery. Every output needs an audit trail. A document that cannot be defended in a disciplinary proceeding is worse than one that was never automated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common pitfalls:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firms that route every template change through IT create a bottleneck that kills adoption. Platforms built on low-code editors let attorneys update clause logic themselves, keeping the system current without a development queue.&lt;/li&gt;
&lt;li&gt;Pricing for document automation software ranges from free AI generators for basic use to enterprise subscriptions with advanced workflow and integration features. Implementation costs and ongoing support fees are often underestimated in initial budgets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pro Tip:&lt;/strong&gt; &lt;em&gt;Before signing any vendor contract, ask specifically whether the platform processes your documents on private managed infrastructure or routes data through a shared public AI model. Attorney-client privilege does not survive on infrastructure that retains or trains on your client data. This is a non-negotiable due diligence question for any New York firm.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Subodhkc brings AI governance to legal automation
&lt;/h2&gt;

&lt;p&gt;Legal document automation does not end at document generation. The harder problem is governing the AI that produces those documents: ensuring outputs are auditable, compliant with New York's AI-specific regulations including NYC Local Law 144, and defensible under professional responsibility standards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv88iapzt36xate7m8pfc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv88iapzt36xate7m8pfc.jpg" alt="Subodhkc" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Subodhkc addresses that layer directly. Where LEAP, Litify, and Legal Outsourcing 2.0 handle the document workflow, Subodhkc provides the &lt;a href="https://subodhkc.com/haiec" rel="noopener noreferrer"&gt;AI governance architecture&lt;/a&gt; that sits beneath it: deterministic compliance frameworks, audit trail design, and risk controls that make AI-generated legal work defensible at the system level, not just the document level. Its HAIEC platform applies structured governance logic to AI operations, giving legal teams the technical evidence trail that regulators and bar associations increasingly expect.&lt;/p&gt;

&lt;p&gt;For New York legal professionals who have already chosen or are evaluating a document automation platform, Subodhkc's &lt;a href="https://subodhkc.com/advisory" rel="noopener noreferrer"&gt;advisory and governance services&lt;/a&gt; answer the question those platforms leave open: how do you prove the AI did what it was supposed to do? That is the accountability layer the market is still building toward, and it is where Subodhkc operates. Start with a governance assessment at subodhkc.com/haiec.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;The strongest legal document automation strategy pairs the right platform with a governed AI architecture that produces auditable, defensible outputs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Point&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Match provider to firm size&lt;/td&gt;
&lt;td&gt;LEAP suits small to medium firms; Litify targets enterprise legal teams; Legal Outsourcing 2.0 fits high-volume review needs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ISO 27001 certification matters&lt;/td&gt;
&lt;td&gt;Legal Outsourcing 2.0 holds ISO 27001 certification, a concrete compliance signal for firms handling sensitive data under New York bar ethics guidance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Template mapping is foundational&lt;/td&gt;
&lt;td&gt;A one-time onboarding session configures clause logic and formatting; the quality of this phase determines every document the system produces.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Governed workflows protect defensibility&lt;/td&gt;
&lt;td&gt;AI generation paired with human review gates and a full audit trail keeps automated documents compliant and professionally defensible.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subodhkc adds the governance layer&lt;/td&gt;
&lt;td&gt;Subodhkc's HAIEC platform provides deterministic AI governance and audit trail architecture for legal teams operating automated document workflows.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Recommended
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://subodhkc.com/products/courtcase" rel="noopener noreferrer"&gt;CourtCase - Legal Document Organization Tool (Coming Soon)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://subodhkc.com/products/doc-timeline" rel="noopener noreferrer"&gt;Doc Timeline Generator - AI-Powered Document Timeline Extraction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://subodhkc.com" rel="noopener noreferrer"&gt;Subodh KC | AI Systems Architect &amp;amp; Governance Expert&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://subodhkc.com/blog/legal-document-automation" rel="noopener noreferrer"&gt;subodhkc.com&lt;/a&gt;. Learn more about the &lt;a href="https://haiec.com" rel="noopener noreferrer"&gt;HAIEC AI Governance Platform&lt;/a&gt;. Follow for more on AI governance, enterprise architecture, and compliance engineering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>legaltech</category>
      <category>bestdocumentaiplatfo</category>
      <category>compliancedocumentau</category>
      <category>compliancedocumentin</category>
    </item>
  </channel>
</rss>
