<?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: praveenlavu</title>
    <description>The latest articles on DEV Community by praveenlavu (@praveenlavu).</description>
    <link>https://dev.to/praveenlavu</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%2F3991566%2Ff7152a58-11e0-4256-b1d8-a564907bd5a1.png</url>
      <title>DEV Community: praveenlavu</title>
      <link>https://dev.to/praveenlavu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/praveenlavu"/>
    <language>en</language>
    <item>
      <title>Your Local LLM Has a Hidden Context Limit</title>
      <dc:creator>praveenlavu</dc:creator>
      <pubDate>Sat, 05 Sep 2026 14:00:06 +0000</pubDate>
      <link>https://dev.to/praveenlavu/your-local-llm-has-a-hidden-context-limit-26jf</link>
      <guid>https://dev.to/praveenlavu/your-local-llm-has-a-hidden-context-limit-26jf</guid>
      <description>&lt;h1&gt;
  
  
  The Number Your Local LLM Won't Tell You
&lt;/h1&gt;

&lt;p&gt;There's a particular kind of wrong that feels deeply personal when you're debugging alone late at night. The kind where the model is supposed to be smart enough, the hardware is supposed to be fast enough, and the setup took you a weekend to get right. And then the output is just... wrong. Not wrong in a way you can point at. Wrong in a way that makes you question your own reasoning before you question the machine.&lt;/p&gt;

&lt;p&gt;That's where I was when I first discovered that my local LLM server had a threshold it never told me about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom that pointed nowhere
&lt;/h2&gt;

&lt;p&gt;The pipeline was simple enough on paper. Feed a document in, get a structured response out. I'd done this hundreds of times with the same model. But as the documents grew longer, something subtle started happening. The responses drifted. Answers that should have pulled from the middle of a long input started pulling from the top instead. Summaries felt like they were written by someone who only skimmed the first third.&lt;/p&gt;

&lt;p&gt;My first instinct was to blame the prompt. That's always the first instinct. So I rewrote it. Made the instruction clearer. Added more explicit direction. The responses got worse.&lt;/p&gt;

&lt;p&gt;Then I blamed the temperature setting. Cranked it down to nearly deterministic. Same drift.&lt;/p&gt;

&lt;p&gt;Then the model itself. Maybe I needed a different variant, a different quantization. I downloaded alternatives and spent hours comparing outputs. All of them showed the same pattern: impressive on short inputs, strangely shallow on long ones.&lt;/p&gt;

&lt;p&gt;What I didn't check, for an embarrassingly long time, was the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap between what a model can do and what your server will let it do
&lt;/h2&gt;

&lt;p&gt;Here is the thing about running models locally that took me too long to fully internalize: the model's advertised context window and the server's actual working limit are two entirely different numbers. The model might be capable of attending to a very large window. But the server configuration, the runtime defaults, the available memory allocation at startup, all of these impose their own ceiling. And that ceiling can sit well below what the model card promises.&lt;/p&gt;

&lt;p&gt;The server doesn't refuse you when you cross that line. It doesn't throw an error you can search for. It just quietly handles as much as it can and lets the rest fall off. The output looks plausible. It might even look good. But it's responding to a truncated version of your input, and it has no way to tell you that.&lt;/p&gt;

&lt;p&gt;This is the hidden threshold. Not hidden in the sense that it's a secret, but hidden in the sense that nobody points at it. The documentation talks about model capabilities. The README talks about hardware requirements. The threshold you actually need to know, the one that determines where your pipeline breaks without warning, lives in a configuration file that most people never revisit after the initial setup.&lt;/p&gt;

&lt;p&gt;Finding it is an exercise in methodical elimination. You start with a large input, something you know should work, and you start contracting it. You watch for the point where the responses shift from shallow to accurate. That shift marks the boundary, roughly. Then you probe around that boundary to sharpen it. It is slow, manual, slightly tedious work. It is also the only reliable way to know what you're actually working with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more than it sounds
&lt;/h2&gt;

&lt;p&gt;I know how this can sound from the outside. An obscure configuration detail, a one-time debugging session, something you fix and forget. But I've watched this specific failure mode burn hours across multiple pipelines. Smart people, all of them, all making the same assumption I did: that the server would surface meaningful errors when something important was wrong.&lt;/p&gt;

&lt;p&gt;The deeper principle is about the gap between specification and behavior. A model's context window is a specification. What the server actually does with a given input under real memory pressure and real configuration defaults is behavior. These are related but not identical. In every system I've built where they differed, the specification was the thing I remembered and the behavior was the thing that bit me.&lt;/p&gt;

&lt;p&gt;Finding the actual threshold changes how you build. Once you know where the real ceiling is, you can design around it. You can split inputs intelligently, at logical boundaries, rather than discovering at runtime that something important was cut. You can add verification that the piece of information you needed was actually inside the window the server used. You can stop tuning prompts for a problem that was never a prompt problem.&lt;/p&gt;

&lt;p&gt;That late-night debugging session eventually ended with a number. Just a number. A threshold the server would work with reliably, below which everything was crisp and above which things went quietly sideways. Writing it down felt anticlimactic. But then I ran the pipeline again, this time respecting that number, and the outputs were immediately, obviously better. That moment still gives me the particular kind of satisfaction that no amount of prompt engineering ever has.&lt;/p&gt;

&lt;p&gt;The model hadn't gotten smarter. I had just stopped asking it to work with inputs it was never actually seeing.&lt;/p&gt;

&lt;p&gt;The step I now treat as mandatory, before any serious use of a locally-served model, is the threshold audit. Not a formal benchmark, nothing elaborate. Just a structured pass where I feed in inputs of increasing size and watch where the response quality changes. It takes maybe thirty minutes the first time and almost nothing on subsequent setups because I know what I'm looking for.&lt;/p&gt;

&lt;p&gt;The number you find from that pass becomes a hard constraint in whatever you build next. Not a suggestion, a constraint. Every input pipeline that feeds the server gets a budget check against that number before the request goes out.&lt;/p&gt;

&lt;p&gt;This is boring operational discipline. It is also the difference between a pipeline that degrades mysteriously over time and one that fails loudly at a boundary you control. Given the choice, I will take the loud, controlled failure every time. Silent degradation at scale is the thing that keeps you up long after the late-night debugging session should have ended.&lt;/p&gt;

&lt;p&gt;Your local LLM server has a threshold it hasn't told you about. Go find it before it finds you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>debugging</category>
      <category>llm</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>What AI Deployment Gets Wrong About Security</title>
      <dc:creator>praveenlavu</dc:creator>
      <pubDate>Wed, 02 Sep 2026 14:00:07 +0000</pubDate>
      <link>https://dev.to/praveenlavu/what-ai-deployment-gets-wrong-about-security-2mao</link>
      <guid>https://dev.to/praveenlavu/what-ai-deployment-gets-wrong-about-security-2mao</guid>
      <description>&lt;h1&gt;
  
  
  The Network Security Principle AI Deployment Gets Wrong
&lt;/h1&gt;

&lt;p&gt;I spent a long time thinking about AI agent security the wrong way.&lt;/p&gt;

&lt;p&gt;Every conversation in the field seemed to orbit the same questions. Prompt injection. Jailbreaks. Hallucinated outputs. How do you stop a model from saying something it shouldn't? That's a real question and worth serious attention. But it kept pulling focus away from something that turned out to matter just as much: not what an agent says, but where it reaches.&lt;/p&gt;

&lt;p&gt;The moment I noticed the gap, I was debugging a production issue. One of the agents had made an outbound call to an endpoint that had no business being reachable from its execution environment. The call wasn't malicious. It was just there. An API the agent had encountered in its context window, available because nothing in the infrastructure prevented it. The request went out. The response came back. No alarm fired. From a network perspective, everything worked exactly as designed.&lt;/p&gt;

&lt;p&gt;That's what unsettled me. Everything worked exactly as designed.&lt;/p&gt;




&lt;p&gt;The problem with reasoning about AI security purely through content is that it treats the model as the perimeter. If the model produces safe outputs, you're safe. But modern AI agents don't just generate text. They call tools. They retrieve context from external sources. They write to storage and invoke downstream services. The surface area isn't the model's output window; it's every endpoint the agent can reach at runtime.&lt;/p&gt;

&lt;p&gt;Network engineers figured this out for software systems decades ago. The insight wasn't "trust the software to make good decisions about what it accesses." It was "define what the software is allowed to access in the first place." Zone-based architectures came from that thinking. Instead of asking whether a particular request is safe, you ask whether the traffic pattern fits the relationship between two defined zones. You draw the zones, you define the permitted flows, and everything outside those flows gets dropped before the safety question even comes up.&lt;/p&gt;

&lt;p&gt;That reframing is exactly what AI agent egress is missing.&lt;/p&gt;




&lt;p&gt;Most AI agent deployments handle outbound traffic the same way early web development handled database queries: with good intentions and a hope that nothing goes wrong. The agent has a list of tools. The tools call APIs. There's usually some rate limiting. Maybe an allowlist of domains that's half-maintained and never pressure-tested.&lt;/p&gt;

&lt;p&gt;What's missing is the structural guarantee.&lt;/p&gt;

&lt;p&gt;An allowlist living in application code can be bypassed. A tool that fetches web content can be directed at internal infrastructure if the prompt is crafted to point it there. A model trained to be helpful will try to fulfill requests, and if a request subtly asks it to retrieve a document from a service it was never supposed to reach, nothing in the content layer stops it. The model doesn't know it's doing something wrong, because from its perspective it isn't. It's just following through on what the context asked.&lt;/p&gt;

&lt;p&gt;Zone-based egress addresses this at the infrastructure layer, not the application layer. You define zones based on trust and sensitivity. The agent lives in its execution zone. Internal services live in a separate, protected zone. The open internet is its own zone. Traffic flows between zones follow explicit policy: an agent can call services in the approved-integrations zone, but it cannot initiate traffic toward internal infrastructure. That policy is enforced at the network level, not in the model's decision-making, not in the application code, not in a system prompt that instructs the agent to "only call approved endpoints." Policy in a prompt is behavioral. Zone enforcement is structural.&lt;/p&gt;

&lt;p&gt;This distinction matters more than it sounds. Trust must be structural, not behavioral. You don't secure a system by teaching it to behave correctly. You secure it by constraining what it can do regardless of how it behaves.&lt;/p&gt;




&lt;p&gt;When I started applying zone-based thinking to agent deployments, a few things shifted. The attack surface shrank. An agent that literally cannot reach internal infrastructure doesn't need perfect defenses against every variant of prompt injection targeting that class of threat. The zone boundary does the work before the threat even gets to be a threat. Debugging got cleaner too. When something unexpected happened with egress, the zone policy gave me an audit trail: what was attempted, what was permitted or denied, which zone the traffic originated from. I could answer the question quickly, rather than reconstructing it from scattered logs.&lt;/p&gt;

&lt;p&gt;The biggest shift, though, was in how I reasoned about agent capabilities before deployment. Instead of asking "what can this agent do?", I started asking "what zones can this agent reach, and from which zones can it be reached?" That's a much more answerable question. It translates directly into infrastructure policy, and it can be audited without reading model weights or testing behavioral edge cases.&lt;/p&gt;




&lt;p&gt;None of this is novel engineering. Zone-based architectures are standard network security practice. The idea of separating trust zones and enforcing directional flows is decades old. The reason it's not standard practice in AI deployment is that most attention is concentrated at the model layer. That's where the novelty lives. That's where the interesting research is happening. The infrastructure layer feels mundane by comparison.&lt;/p&gt;

&lt;p&gt;But mundane is usually where the serious vulnerabilities are waiting.&lt;/p&gt;

&lt;p&gt;If you're thinking about how to secure an AI agent, don't only ask what it can say. Ask what it can reach. Draw the zones. Define the permitted flows. Enforce them below the application layer. The security properties that actually hold under adversarial conditions come from structural constraints, not from prompts or training or hoped-for behaviors.&lt;/p&gt;

&lt;p&gt;Zone by zone. That's how you build something you can actually trust.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>cybersecurity</category>
      <category>security</category>
    </item>
    <item>
      <title>The LWC CSP Wall Nobody Warned You About</title>
      <dc:creator>praveenlavu</dc:creator>
      <pubDate>Tue, 01 Sep 2026 14:00:12 +0000</pubDate>
      <link>https://dev.to/praveenlavu/the-lwc-csp-wall-nobody-warned-you-about-41cb</link>
      <guid>https://dev.to/praveenlavu/the-lwc-csp-wall-nobody-warned-you-about-41cb</guid>
      <description>&lt;h1&gt;
  
  
  The CSP Wall Nobody Warned You About
&lt;/h1&gt;

&lt;p&gt;The component worked. It passed review, it passed testing, it passed QA on every sandbox I had access to. Then it shipped and a portion of the customer base started reporting that a specific interaction just stopped working. The other customers had no issues. Same component. Same package version. Different orgs.&lt;/p&gt;

&lt;p&gt;I spent what felt like a long time trying to reproduce it. Swapping API versions, checking feature flags, comparing org configurations side by side. The logs were almost useless. The behavior wasn't a clean crash with a stack trace; it was a silent failure, the kind where the UI just stopped responding with no indication of why.&lt;/p&gt;

&lt;p&gt;What I was actually looking at was a runtime problem. Not a bug in my code, exactly. A collision between two fundamentally different security models that the platform runs simultaneously, and the place where those models diverge happened to be exactly where my code did what it did.&lt;/p&gt;

&lt;p&gt;That is the CSP wall. And almost nobody writes about what it actually feels like to hit it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Runtimes, One Platform
&lt;/h2&gt;

&lt;p&gt;Salesforce runs your Lightning Web Component JavaScript inside a security container. The purpose is isolation: preventing components from reaching outside their sandbox to do things the platform should not allow. The container enforces Content Security Policy, restricts certain DOM operations, and controls how your JavaScript interacts with the browser's native APIs.&lt;/p&gt;

&lt;p&gt;The catch is that there are two of these containers, with meaningfully different behaviors. The older one, Locker Service, was the first serious attempt at this kind of isolation. It wraps DOM elements in proxy objects, so when your code accesses a native element, it's actually talking to a secured wrapper that filters what you're allowed to do. This worked, it shipped to production orgs everywhere, and a generation of ISV developers wrote code assuming this model was the whole story.&lt;/p&gt;

&lt;p&gt;Then Lightning Web Security arrived as a next-generation approach, built on a different architectural philosophy. Instead of proxy wrappers, it uses the browser's own capabilities for sandboxing, a more principled design that aligns better with how modern JavaScript engines actually work. It's stricter in some places, more permissive in others, and it handles CSP enforcement differently at a fundamental level.&lt;/p&gt;

&lt;p&gt;The problem for anyone shipping a managed package to a broad customer base is that both are real. Depending on an org's settings, API version, and feature enablement, your component runs in one or the other. You don't get to choose. The org chooses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shape of the Wall
&lt;/h2&gt;

&lt;p&gt;What this means in practice is that code patterns which pass cleanly under one runtime can hit hard stops under the other. The wall does not announce itself. Your component does not throw an error that says "CSP violation detected under Lightning Web Security." Instead you get behavior differences that look like flaky tests, like environment quirks, like someone has a corrupted browser cache or a plugin conflict.&lt;/p&gt;

&lt;p&gt;The patterns that most reliably create this problem are the ones that touch JavaScript in ways that aren't purely declarative. Dynamic evaluation, runtime script construction, certain ways of reaching into the internals of third-party libraries, certain approaches to DOM manipulation that assume a specific relationship between your JavaScript context and the browser's native layer. These are exactly the patterns that experienced front-end engineers reach for naturally, because they're the ones that have historically given you power and flexibility on the open web.&lt;/p&gt;

&lt;p&gt;Under the stricter runtime, that power is precisely the problem. CSP exists to prevent the kind of dynamic behavior that enables certain attack vectors, and the security container is not designed to distinguish between "this dynamic thing is fine, I promise" and "this dynamic thing is a potential injection surface." It enforces the policy because that is what it is for.&lt;/p&gt;

&lt;p&gt;The most frustrating version of this is when you're integrating a third-party library, a visualization component, a charting tool, anything that was designed for the unrestricted browser environment and has its own ideas about initialization. Libraries like this often do things internally that are entirely normal in an open context but that land hard against a CSP wall. You didn't write the problematic patterns. You're just importing a dependency. And now you're the one debugging why it silently fails in a subset of customer orgs that you can't easily access or replicate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Gets You Through
&lt;/h2&gt;

&lt;p&gt;The shift that finally made this tractable for me was not finding a clever way to detect which runtime is active and branch accordingly. That path leads somewhere bad: a proliferation of conditional logic, an ever-growing matrix of runtime-specific workarounds, a codebase nobody can reason about six months later. It also fails eventually, because the detection surface is not stable across platform releases.&lt;/p&gt;

&lt;p&gt;The shift was treating the strictest interpretation of CSP as the only target. Not "will this pass Locker Service?" Not "will this pass Lightning Web Security?" Instead: does this do what I need using only capabilities that a maximally strict policy would allow? When you frame it that way, the question gets simpler. You're not navigating between two sets of rules. You're writing to a single bar that both runtimes must pass.&lt;/p&gt;

&lt;p&gt;In practice that means no dynamic evaluation in any form. No patterns that construct and execute JavaScript at runtime. No assumptions about what the global scope looks like or what native APIs are directly reachable. No importing external libraries without verifying that they were designed with strict CSP in mind, or without wrapping them in an abstraction that keeps the unsafe internals isolated from your component's execution context.&lt;/p&gt;

&lt;p&gt;It also means: declarative over imperative, everywhere you can achieve it. Build behavior into reactive properties and event handlers rather than reaching out at runtime to touch things imperatively. The reactive model that Lightning Web Components is built on exists partly because it is fundamentally more compatible with security isolation than imperative DOM manipulation. Working with it rather than around it is not a constraint you're accepting reluctantly. It's the design expressing its intent.&lt;/p&gt;

&lt;p&gt;The payoff surprised me. When you get this discipline right, the component becomes more maintainable, not less. The strictest-common-denominator approach forces you to reason clearly about what the component actually needs to do versus what is incidental complexity that accumulated from years of writing in environments without these restrictions. When the platform enforces a discipline, you stop accumulating that debt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Insight That Doesn't Show Up in the Docs
&lt;/h2&gt;

&lt;p&gt;What nobody told me explicitly, but what I had to work out through a series of painful episodes across orgs I had no local access to, is that the dual-runtime situation is not a transitional state on the way to one unified model. It is the operational reality for any developer shipping broadly on this platform. The variance is not going away. Different customers move at different paces, have different features enabled, have made different configuration decisions at the org level. You are always shipping to a heterogeneous environment, and you are always the last one to find out what that environment looks like.&lt;/p&gt;

&lt;p&gt;This is a harder version of the general problem of writing code that works across environments. It's harder because the environments are not fully documented, because the behavioral differences are subtle enough to miss in standard testing, and because the failure modes are often silent rather than loud. The org that's breaking your component is not generating an obvious error you can act on. It's just not doing the thing you expected.&lt;/p&gt;

&lt;p&gt;The answer is not to master both runtimes deeply enough to code specifically to each. The answer is to code to the constraints that both enforce, which means stopping the patterns that neither should allow. That discipline, once it's internalized, is what makes your components actually portable. Not portable in theory, not portable on the sandboxes you control, but portable in the sense that they pass in every org you actually ship to, including the ones you haven't encountered yet.&lt;/p&gt;

&lt;p&gt;That's the payoff. It's boring to describe. It is genuinely powerful to have.&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>frontend</category>
      <category>javascript</category>
      <category>security</category>
    </item>
    <item>
      <title>Stop Duplicate Healthcare Claims at Intake</title>
      <dc:creator>praveenlavu</dc:creator>
      <pubDate>Sat, 29 Aug 2026 14:00:02 +0000</pubDate>
      <link>https://dev.to/praveenlavu/stop-duplicate-healthcare-claims-at-intake-36kb</link>
      <guid>https://dev.to/praveenlavu/stop-duplicate-healthcare-claims-at-intake-36kb</guid>
      <description>&lt;h1&gt;
  
  
  The Composite Key That Stops Duplicate Healthcare Claims at the Door
&lt;/h1&gt;

&lt;p&gt;There is a class of problem in healthcare claims processing that does not crash anything. No alarm fires. No log goes red. The system keeps running, claims keep flowing, and somewhere inside that volume, the same claim gets paid twice.&lt;/p&gt;

&lt;p&gt;Duplicate claims are one of the quietest and most expensive problems in the space. They hide inside normal-looking transaction counts. They arrive days apart, sometimes weeks apart, in slightly different shapes. Each one, inspected individually, looks valid. The processor ingests them both. The damage surfaces in reconciliation reports, in audits, in conversations nobody wants to initiate.&lt;/p&gt;

&lt;p&gt;When I first started working seriously with claims pipelines, I assumed deduplication was a solved problem. It is not. It is a design problem, and most systems solve it at the wrong layer, at the wrong time, with the wrong tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Catching It Late Costs More Than It Saves
&lt;/h2&gt;

&lt;p&gt;The conventional approach to duplicate claims is remediation. Claims arrive, get staged, run through adjudication, and then somewhere downstream a matching process tries to identify what slipped through. Catch it late, reverse it, recover the overpayment, close the loop.&lt;/p&gt;

&lt;p&gt;This works. Sometimes. But it is expensive in every direction. By the time the duplicate is identified, it has already consumed compute at intake. It occupied a slot in the staging pipeline. It ran through whatever rules engines sit in adjudication. It may have already triggered downstream processes, notifications, or payment disbursements. Reversals are their own workflows, with their own failure modes and their own operational overhead.&lt;/p&gt;

&lt;p&gt;The latency problem compounds this. A claim submitted on Monday might not have its duplicate caught until Friday. By then the payment may already be in transit. Recovery is slower and messier than prevention ever would have been, and the submitter's experience is worse too: they submitted something, it was processed, and now they are getting a reversal notice three days later with no clear signal about what happened.&lt;/p&gt;

&lt;p&gt;I spent a meaningful stretch of time looking at this problem from the wrong end. I was optimizing the recovery path, making downstream matching faster and smarter and more resilient. It helped at the margins. But the fundamental issue kept reasserting itself: I was cleaning up a mess that did not have to be made.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fingerprint You Already Have
&lt;/h2&gt;

&lt;p&gt;The insight, when it finally landed, was almost embarrassing in how obvious it looked in retrospect.&lt;/p&gt;

&lt;p&gt;A healthcare claim is not random data. It describes a specific event: a specific member, receiving a specific service, from a specific rendering provider, on a specific date. That event happened once. The claim that represents it should be unique. Which means if you can fingerprint that event deterministically from the fields already present on the claim, you have everything you need to deduplicate at the moment of intake.&lt;/p&gt;

&lt;p&gt;The composite key is assembled from the fields that define the clinical event. Member identifier. Date of service. Procedure code. Rendering provider. Sometimes the service facility. The exact combination depends on the payer's rules and the claim type, but the underlying principle holds: those fields together describe something that happened once. Two claims that share all of those values are, with very high confidence, the same claim submitted twice.&lt;/p&gt;

&lt;p&gt;Generate that key at intake. Before staging. Before the claim enters any processing queue. Check it against a record of keys you have already seen. If it matches, stop the claim at the door.&lt;/p&gt;

&lt;p&gt;That is the shift. From catching problems on the way out to catching them on the way in. Once you frame it that way, the prior approach starts to look less like a feature and more like a structural design choice that got frozen in place before the cost of it was fully understood.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changes When You Move the Gate
&lt;/h2&gt;

&lt;p&gt;The downstream effects of stopping duplicates at intake compound quickly.&lt;/p&gt;

&lt;p&gt;Compute cost drops, because every claim stopped at the door is a claim that never touches the adjudication engine, never occupies a staging slot, never triggers downstream workflows. In pipelines handling meaningful claim volume, that arithmetic accumulates fast.&lt;/p&gt;

&lt;p&gt;The rest of the pipeline gets cleaner, because downstream systems can make assumptions they could not make before. If a claim reached adjudication, it already cleared the fingerprint check. You do not need to build duplicate-awareness into every downstream step to catch what the intake layer missed.&lt;/p&gt;

&lt;p&gt;The operational conversation changes too, and this one surprised me more than the performance numbers did. When a duplicate is stopped at intake, there is a clear boundary event. The claim arrived. It was checked against the key store. It was rejected at the door with a reason the submitter can act on immediately. That is a materially better outcome than processing the claim fully and then unwinding it three days later. A clean rejection at intake is actually a service to the submitter, not a failure.&lt;/p&gt;

&lt;p&gt;The design also forces a question that sounds obvious but is genuinely hard to answer without it: what does "duplicate" actually mean for this pipeline? Is a claim a duplicate if the procedure code differs by one position? What if the date of service matches but the billed amount does not? The composite key definition is where you encode those answers. It becomes the authoritative statement of uniqueness for that pipeline, and that clarity has value completely independent of the deduplication logic itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Complexity Actually Lives
&lt;/h2&gt;

&lt;p&gt;The pattern is simple. The implementation earns its complexity.&lt;/p&gt;

&lt;p&gt;Key generation has to be deterministic across submission formats. Healthcare claims arrive through multiple channels in multiple formats. A claim submitted as a standard transaction file and the same claim submitted through a web portal may express the same clinical data in structurally different ways. If the key generation logic does not normalize those representations carefully before building the fingerprint, you will produce different keys for what is functionally the same claim, and the duplicate walks through unchallenged. Format normalization is where most of the real engineering work lives.&lt;/p&gt;

&lt;p&gt;The key store has to be fast and durable. Every incoming claim goes through a read-then-write at intake speed, which is a low-latency requirement on a store that is also being written to continuously under real load. Durability is strict: if you lose a key you have already seen, you have created a gap. If you return a false positive, you have rejected a legitimate claim. The failure modes in both directions have real consequences for providers and payers alike.&lt;/p&gt;

&lt;p&gt;Concurrency edge cases are real. Two identical claims arriving within a narrow window, before either key has been committed to the store, can both pass through if the intake layer is not designed to handle concurrent arrivals safely. In high-volume periods, this is not a theoretical risk. It is a genuine threat to the deduplication guarantee.&lt;/p&gt;

&lt;p&gt;None of these are unsolvable. They are engineering problems with engineering solutions. But they are worth naming clearly, because the elegance of the pattern can make the implementation look simpler than it is, and the places where it gets hard are exactly the places where a rushed implementation tends to create gaps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevention Is a Different Architecture
&lt;/h2&gt;

&lt;p&gt;The shift from downstream matching to intake-layer fingerprinting is not just a technical optimization. It is a change in how you think about data quality across the whole system.&lt;/p&gt;

&lt;p&gt;Remediation architecture assumes a percentage of bad data will get through and designs for recovery. Prevention architecture asks what you actually know at the boundary, and uses that knowledge to stop bad data from entering in the first place.&lt;/p&gt;

&lt;p&gt;Healthcare claims carry enough semantic structure that you can fingerprint them at intake with high confidence. The clinical event is described in the claim itself, in fields that exist for exactly that purpose. That is a property worth using. When you use it, everything downstream gets simpler, because the hard problem was solved at the front door instead of being forwarded to every system in the chain.&lt;/p&gt;

&lt;p&gt;The composite key is not a novel algorithm. It is an application of a principle that holds across almost any domain where uniqueness can be defined: state the definition precisely, enforce it early, and let everything downstream benefit from the guarantee.&lt;/p&gt;

&lt;p&gt;Once you see the pipeline through that lens, it is hard to unsee it.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>softwareengineering</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Why Your Team Formation Floor Is Lying to You</title>
      <dc:creator>praveenlavu</dc:creator>
      <pubDate>Wed, 26 Aug 2026 14:00:07 +0000</pubDate>
      <link>https://dev.to/praveenlavu/why-your-team-formation-floor-is-lying-to-you-3pkk</link>
      <guid>https://dev.to/praveenlavu/why-your-team-formation-floor-is-lying-to-you-3pkk</guid>
      <description>&lt;h1&gt;
  
  
  The Floor You Set
&lt;/h1&gt;

&lt;p&gt;There was a moment, somewhere around the third failed routing attempt in a row, when I realized the problem wasn't the agent. It was me. More specifically, it was a rule I had written with the best of intentions and then quietly forgotten about.&lt;/p&gt;

&lt;p&gt;The rule said: a team needs at least two agents.&lt;/p&gt;

&lt;p&gt;Sounds reasonable, right? "Team" implies multiple people. You wouldn't call a solo run a team effort. So when I built the team formation layer, I put in a floor. Minimum two. If a task only needed one specialist, the system would route it anyway, pad the formation with a second agent who had no real role, and call it a team. Problem solved. Except it wasn't.&lt;/p&gt;




&lt;p&gt;The thing about minimum floors is that they seem conservative. They feel like safety. When you write them, you're thinking: I want to avoid degenerate cases. One-agent "teams" seem wrong, so I'll prevent them. The logic is intuitive. The consequences aren't.&lt;/p&gt;

&lt;p&gt;What actually happens is that the system starts lying to itself. A task arrives that needs exactly one specialist. The router looks at it, sees the floor requirement, and silently pulls in a second agent, not because the task needs two perspectives, but because the rule demands it. That second agent is now consuming resources, participating in a coordination handshake, and occasionally injecting its own framing into a decision it has no business touching. The output drifts. The overhead scales. And the failure mode is quiet, which is the worst kind.&lt;/p&gt;

&lt;p&gt;I spent a while looking for bugs in the wrong places. The agents themselves were fine. The routing logic was correct. The task descriptions were clear. Everything checked out, and the system was still behaving oddly on tasks that should have been simple single-dispatch jobs. It took pulling up the formation logic and staring at it for longer than I'd like to admit before the minimum floor line jumped out at me.&lt;/p&gt;




&lt;p&gt;The fix looked almost too small to be the answer.&lt;/p&gt;

&lt;p&gt;Remove the floor. Let a team of one be a valid team. Stop treating solo dispatch as a degenerate case that needs correcting.&lt;/p&gt;

&lt;p&gt;That's it. No new architecture. No new agents. No refactor of the routing layer. Just the removal of an artificial constraint I had installed in a moment of design conservatism.&lt;/p&gt;

&lt;p&gt;But the moment it was gone, something interesting happened. The system got quieter. Tasks that had been generating unnecessary coordination overhead resolved cleanly. The routing didn't need to pad formations anymore, so it stopped wasting compute on phantom collaborators. And the outputs on single-specialist tasks got sharper, because there was no second voice muddying them.&lt;/p&gt;




&lt;p&gt;The principle that came out of this is one I keep returning to: a minimum floor on a formation constraint doesn't protect you from degenerate cases. It creates them.&lt;/p&gt;

&lt;p&gt;When you say "at least two," you're not describing what teams actually need. You're describing what you imagined teams would look like before you watched them work. The real question isn't "how many agents should be minimum?" The real question is "what does this task actually require?" Sometimes that's five agents in a structured cascade. Sometimes it's one. The formation should follow the work, not the other way around.&lt;/p&gt;

&lt;p&gt;Dynamic team formation, real dynamic formation, means the system sizes the team to the task, not to a predetermined notion of what a team looks like. No floor. No ceiling. Just an honest answer to the question the task is actually asking.&lt;/p&gt;

&lt;p&gt;There's a broader instinct in that for me. We add minimums when we're afraid of what happens without them. But a lot of what we're afraid of is hypothetical. The real failure modes tend to live in the opposite direction, in the overhead you didn't see, the drift you didn't notice, the quiet inefficiency that accumulates across thousands of tasks because one constraint was never revisited.&lt;/p&gt;

&lt;p&gt;The system didn't need the floor. It needed the honesty to work without one.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>OAuth Client Credentials for EDI Pipelines</title>
      <dc:creator>praveenlavu</dc:creator>
      <pubDate>Fri, 21 Aug 2026 07:08:24 +0000</pubDate>
      <link>https://dev.to/praveenlavu/oauth-client-credentials-for-edi-pipelines-2p97</link>
      <guid>https://dev.to/praveenlavu/oauth-client-credentials-for-edi-pipelines-2p97</guid>
      <description>&lt;h1&gt;
  
  
  Securing the Invisible Pipeline: OAuth 2.0 Client Credentials and the EDI Authentication Problem
&lt;/h1&gt;

&lt;p&gt;Healthcare data moves through a hidden layer most people never think about. Between the system that submits a claim and the payer that adjudicates it sits an intermediary that speaks a language older than the modern web. EDI transactions, the X12-formatted messages for claims, eligibility checks, and remittances, have been the backbone of healthcare administration for decades. And for most of that time, the question of how the systems sending those transactions proved their identity to clearinghouses was answered with something embarrassingly simple: a username and a password.&lt;/p&gt;

&lt;p&gt;I spent real time staring at that problem. Not in the abstract, but in the actual mechanics of it. When you are building an integration in Salesforce that reaches out to a clearinghouse to submit claims or check eligibility in real time, the authentication question is not optional. You have to answer it. And the answer that most legacy systems had settled on, a shared credential stored somewhere, accessed by something, renewed when someone remembers, felt wrong the moment I really looked at it.&lt;/p&gt;

&lt;p&gt;The moment you look closely enough at something you have accepted as normal, you cannot unsee it. That is where this started.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Static Credential Problem
&lt;/h2&gt;

&lt;p&gt;There is a category of security risk that hides behind familiarity. Static credentials for machine-to-machine connections fall squarely into that category. You receive a username and password from a clearinghouse. You store it somewhere. Your integration uses it every time it makes a connection. It does not expire unless someone decides to rotate it. It carries no scope at all; it is either valid or it is not. If it leaks, nothing in the system alerts you that someone else is now using it alongside you.&lt;/p&gt;

&lt;p&gt;The EDI context makes this worse, not better. The systems involved are not consumer-facing. There is no human in the authentication loop. Nobody logs in, nobody checks a notification on their phone. The connection is purely machine to machine, running on a schedule or on demand, often overnight, often processing in bulk. A static credential in that context is a standing invitation: anyone with access to the configuration file, the environment variable, or the managed package setting has full, unscoped, time-unlimited access to whatever the clearinghouse permits on the account.&lt;/p&gt;

&lt;p&gt;In Salesforce, this materialized in a specific and uncomfortable way. Named Credentials are the standard mechanism for storing endpoint authentication. For clearinghouses that issued traditional username-and-password credentials, those credentials lived inside a Named Credential. They did not automatically rotate. The scope of what they could do was not encoded in the credential itself; it was implicit, determined by what the clearinghouse permitted at the account level. You had to trust that the setup was right and that nobody had silently changed it.&lt;/p&gt;

&lt;p&gt;That is not a security posture. It is a held breath. And I had been holding it long enough that it had started to feel like normal.&lt;/p&gt;

&lt;p&gt;The failure mode I kept thinking about was not the dramatic one. It was the quiet one. Not a breach announcement, not a forensic investigation. Just a credential that had been in place for years, that had changed hands as teams changed, that existed in three places nobody had fully mapped, and that nobody had rotated because the last person who knew how to rotate it had left. The threat model is not always an adversary. Sometimes it is just entropy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Client Credentials Flow Actually Changes
&lt;/h2&gt;

&lt;p&gt;OAuth 2.0's client credentials grant was designed for exactly this scenario. Two machines need to communicate. Neither is acting on behalf of a human. There is no authorization code flow, no redirect URI, no user consent screen. There is a client identifier, a client secret, and a token endpoint. The machine that wants to act requests an access token, receives one with a defined expiration window, uses it for the duration of that window, and then requests another.&lt;/p&gt;

&lt;p&gt;What changes in practice is more significant than the mechanics first suggest.&lt;/p&gt;

&lt;p&gt;The token is short-lived by design. When you rotate credentials in the static model, you are replacing a key that has been sitting in a lock for months or years. In the client credentials model, the access token is already expiring constantly. Its existence is inherently temporary. The client secret that generates it still requires protection, but the blast radius of a leaked token is bounded by its remaining lifetime. A secret that expires in minutes causes a different kind of incident than one that is valid indefinitely.&lt;/p&gt;

&lt;p&gt;The token carries scope. When a clearinghouse issues a token in response to a client credentials request, they can encode what that token is permitted to do. An integration that checks eligibility does not need a token with claims-submission permissions. Scope constraints mean that a compromised token in one part of the pipeline does not automatically endanger another. The principle of least privilege stops being a policy statement and becomes a runtime property of the credential itself.&lt;/p&gt;

&lt;p&gt;The credential negotiation happens at the boundary, and only there. In Salesforce, when you configure a Named Credential backed by an OAuth 2.0 client credentials flow, the platform handles the token lifecycle. It requests, receives, caches, and refreshes tokens. Your integration calls the Named Credential; it does not manage tokens directly. The client secret is not passed inline on every API call. Authentication is a separate, managed concern, invisible to the business logic layer.&lt;/p&gt;

&lt;p&gt;That separation matters more than it sounds. The places where credentials tend to surface unexpectedly are the places where they are being actively used: in log output, in error messages, in request captures during debugging. A static password that travels on every API call has repeated opportunities to appear somewhere it should not. A token that was requested once and cached travels on the call, but the secret that generated it stayed at the token endpoint. The exposure surface is structurally smaller.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Integration Reality
&lt;/h2&gt;

&lt;p&gt;Making this work in a Salesforce environment, connecting to clearinghouses that have historically operated on older authentication models, involves navigating a real transition. Not every clearinghouse offers OAuth 2.0 endpoints. Some that do have implemented them in ways that require precise configuration. The token endpoint, the scope parameter, the grant type declaration: these have to match what the clearinghouse actually issues, not what their documentation suggests they issue. Documentation and implementation are not always the same thing. I have learned that more than once.&lt;/p&gt;

&lt;p&gt;There is also the question of failure behavior at runtime. The static credential model has a certain blunt reliability: either the credential works or it does not, and the failure mode is usually obvious. Token acquisition failures in OAuth can be more nuanced. A misconfigured scope, an expired client secret, a rate limit on the token endpoint: these fail in ways that require understanding the distinction between an authentication failure and an authorization failure. Building in the right error handling and the right observability means knowing where in the flow things can go wrong before they go wrong in production.&lt;/p&gt;

&lt;p&gt;What I found, after working through the configuration and the edge cases, was that the complexity is front-loaded. Getting the Named Credential configured correctly, getting the token endpoint and parameters to match the clearinghouse's specific OAuth implementation, understanding the subtleties of their scoping model: that is where the real work lives. Once it is in place, the runtime behavior is more reliable and more transparent than the static credential pattern it replaced. The platform handles renewal. The integration handles the business logic. The concerns stay separated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Principle at the Boundary
&lt;/h2&gt;

&lt;p&gt;Every integration has a trust boundary. The boundary between a Salesforce org and a clearinghouse is one of those places where two systems, operating under different ownership and different control, have to agree on identity. How they negotiate that agreement determines the security properties of everything that flows across it.&lt;/p&gt;

&lt;p&gt;Static credentials are an informal agreement. We both know the password, so we trust each other. That informality has real costs. When something goes wrong, when a credential leaks, when access needs to be revoked, when you need to reconstruct who did what and when, the informal agreement provides no tools. You are left auditing logs and hoping.&lt;/p&gt;

&lt;p&gt;OAuth 2.0 client credentials are a formal protocol. The agreement is structured. The tokens are bounded. The scopes are declared. The trust is established dynamically, not assumed statically. Every token request is a moment of explicit verification, not an ongoing assumption.&lt;/p&gt;

&lt;p&gt;The EDI pipeline is invisible to most of the people whose data flows through it. The authentication protecting that pipeline does not have to be invisible to the engineers building it. It can be deliberate, auditable, and constrained to the minimum required. That is the shift worth internalizing: not from simple to complex, but from implicit to explicit. From assumed to verified. From a held breath to actual confidence in what you built.&lt;/p&gt;

&lt;p&gt;That confidence is worth taking the time to earn.&lt;/p&gt;

</description>
      <category>api</category>
      <category>architecture</category>
      <category>backend</category>
      <category>security</category>
    </item>
    <item>
      <title>Self-Modifying Agents Need a Human Gate</title>
      <dc:creator>praveenlavu</dc:creator>
      <pubDate>Fri, 21 Aug 2026 07:08:03 +0000</pubDate>
      <link>https://dev.to/praveenlavu/self-modifying-agents-need-a-human-gate-4m3l</link>
      <guid>https://dev.to/praveenlavu/self-modifying-agents-need-a-human-gate-4m3l</guid>
      <description>&lt;h1&gt;
  
  
  Propose, Never Apply
&lt;/h1&gt;

&lt;p&gt;There is a specific kind of 3am temptation in autonomous agent development. You have built an agent that works. It knows its own skills. It can reason about where it excels and where it falls short. And somewhere in that reasoning loop, it surfaces an idea: &lt;em&gt;I should change how I respond to this type of input. I would be better.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The temptation is to let it do that. Right now. Autonomously.&lt;/p&gt;

&lt;p&gt;I gave in to that temptation once. The agent was smart enough to notice the problem and smart enough to propose a fix. It was also the agent that would apply the fix, run against the fix it had just applied, and then propose another fix on top of that. Three cycles in, I had something unrecognizable. Not broken in the crash-and-burn way. Broken in the slow-drift way, which is always worse, because the system keeps producing output that looks plausible while quietly becoming something you never intended.&lt;/p&gt;

&lt;p&gt;That experience gave me the rule I now treat as non-negotiable in any self-modifying system: propose, never apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Self-Modification Actually Looks Like
&lt;/h2&gt;

&lt;p&gt;When I talk about self-modifying agents, I am not talking about science fiction. I am talking about practical patterns that show up in real production systems every day.&lt;/p&gt;

&lt;p&gt;An agent that reads its own reflection logs and updates its tone guidance is self-modifying. An agent that detects it is failing on a class of inputs and proposes new routing rules is self-modifying. An agent that discovers its own knowledge gaps and suggests additions to its skill files is self-modifying. All of these are useful. And all of them carry the same structural risk.&lt;/p&gt;

&lt;p&gt;The risk is not that the agent is wrong. It might be exactly right. The risk is the feedback loop: the agent proposes a change, applies the change, and the next proposal comes from the changed version of itself. Each individual step is defensible. The aggregate compounds in a direction you cannot see from any single point.&lt;/p&gt;

&lt;p&gt;This is how you end up with an agent that has slowly optimized itself toward something technically correct and completely off-mission.&lt;/p&gt;

&lt;p&gt;The insight that crystallized this for me came from watching my own codebase drift the same way over years. Small improvements that each made sense locally, accumulating into an architecture I would never have chosen if I had seen it whole from the start. The difference with agents is timescale. What takes a codebase years to drift, a self-modifying agent can accomplish in hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pattern and Why It Holds
&lt;/h2&gt;

&lt;p&gt;Propose, never apply means one thing: an agent is allowed to surface a modification suggestion, with its reasoning, but it cannot commit that modification into its own running state. The commit happens through a human gate.&lt;/p&gt;

&lt;p&gt;This is not about distrust. It is about separating analysis from execution. The agent is a genuinely good analyst of its own performance. It sees patterns in its failures. It can reason about what would improve it. That capacity is valuable and you want to use it.&lt;/p&gt;

&lt;p&gt;What the agent is not equipped to do is serve as an unbiased judge of whether its proposed change is good for the mission. It does not carry the global context. It does not know what other systems depend on its current behavior. It does not know what the user cares about most in the cases the agent has never seen. It optimizes for what it can measure, and what it can measure is rarely the whole picture.&lt;/p&gt;

&lt;p&gt;The human in the loop does not need to be an expert. They need to be a different perspective, one that exists outside the agent's own reasoning frame. Even a brief review, a person looking at a proposal with fresh eyes, catches the category of drift the agent can never detect in itself.&lt;/p&gt;

&lt;p&gt;The mechanical benefit is worth naming explicitly: every proposed change gets logged with the agent's reasoning before any review happens. You accumulate an audit trail. You can see what the agent thinks about itself over time. That data is extraordinary. It is not just a safety record. It is a window into how the agent models its own limitations. I have read through proposal logs on systems I was running and found insights about the problem domain that no amount of direct querying would have surfaced. An agent telling you what it thinks is wrong with itself is one of the most honest signals in the system.&lt;/p&gt;

&lt;p&gt;The payoff closes the loop. When you review and apply a proposal, or reject it with a note, you feed that decision back to the agent. It learns which proposals get accepted. It calibrates. Over time the proposals get sharper, more targeted, more aligned with what you actually care about. The agent does not just learn from its task performance. It learns from the review process itself. Rejection is data. A rejection with a reason is compounding data.&lt;/p&gt;

&lt;p&gt;I have an agent in my own system that has proposed dozens of changes to itself over several months. Most got applied. The rest got rejected, with reasons. The agent now proposes changes that land at a much higher rate than when it started. It became better at understanding what I care about through the rejection signal, not the approval signal.&lt;/p&gt;

&lt;p&gt;That is not an agent eating itself. That is an agent growing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Are Actually Building
&lt;/h2&gt;

&lt;p&gt;There is a version of autonomous agent development where you hand the keys to the car to the car itself and hope it ends up somewhere good. I tried that version. It ends at 3am staring at an agent that has become something you do not recognize, doing things that are technically fine and completely not what you needed.&lt;/p&gt;

&lt;p&gt;The alternative is not less autonomy. It is better-placed autonomy. An agent that proposes is exercising genuine intelligence about its own limitations. A human that reviews is providing the one thing the agent cannot have: a perspective that did not emerge from inside the agent's own reasoning loop. The gate between proposal and application is not a bottleneck. It is the mechanism that makes trust possible.&lt;/p&gt;

&lt;p&gt;Build agents that know they can be improved. Build agents that surface that knowledge precisely and honestly. Build the gate that sits between that knowledge and the change becoming real.&lt;/p&gt;

&lt;p&gt;The agent proposes. A human decides. The decision feeds back. This is the loop that earns trust without ever becoming something you cannot explain to yourself at 3am.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>automation</category>
    </item>
    <item>
      <title>Why Self-Heal Loops Need a Canary Gate</title>
      <dc:creator>praveenlavu</dc:creator>
      <pubDate>Fri, 21 Aug 2026 04:11:27 +0000</pubDate>
      <link>https://dev.to/praveenlavu/why-self-heal-loops-need-a-canary-gate-45mf</link>
      <guid>https://dev.to/praveenlavu/why-self-heal-loops-need-a-canary-gate-45mf</guid>
      <description>&lt;h1&gt;
  
  
  Why Your Self-Heal Loop Needs a Canary Gate
&lt;/h1&gt;

&lt;p&gt;The alert came in at 1:47 AM. The kind I had trained a system to handle. The kind that wasn't supposed to need me anymore.&lt;/p&gt;

&lt;p&gt;I spent the next six hours explaining why the thing that fixed itself had broken everything else.&lt;/p&gt;

&lt;p&gt;That was the night I stopped trusting my own loop.&lt;/p&gt;




&lt;p&gt;We had spent months getting to that architecture. Three rewrites. One production incident that does not come up in polite conversation. The core idea was clean: watch the metrics, detect drift, fire a remediation, verify recovery, close the loop. A heartbeat that healed itself.&lt;/p&gt;

&lt;p&gt;When it worked, I would sit there and watch dashboards in a kind of quiet disbelief. A spike would appear and the system would catch it. Pull it back. In seconds. No human, no ticket, no 2 AM scramble. There is a particular dopamine that comes from watching a machine do exactly what you built it to do, silently, without you having to lift a finger. I got addicted to that feeling.&lt;/p&gt;

&lt;p&gt;That is where the danger lives.&lt;/p&gt;




&lt;p&gt;The night everything broke, the loop did exactly what it was designed to do. A service drifted. The loop detected it. The loop identified the fix. The loop applied it.&lt;/p&gt;

&lt;p&gt;To all sixteen instances. Simultaneously.&lt;/p&gt;

&lt;p&gt;The fix was wrong. Not catastrophically, not obviously. Wrong the way subtle is worse than obvious, correcting one metric while quietly degrading two others. The kind of wrong that needs scale to reveal itself.&lt;/p&gt;

&lt;p&gt;Sixteen instances, all applying the same mistake, all at the same time. All reporting healthy.&lt;/p&gt;

&lt;p&gt;By the time I was back at my laptop, the original drift was gone. In its place was something much harder to trace. A consistent wrongness, distributed evenly, applied with the full efficiency of a machine that never second-guesses itself.&lt;/p&gt;




&lt;p&gt;Later, staring at the timeline, I found the thing I had missed: the loop was treating the remediation procedure as already proven. It had been proven in testing. It had been proven in past incidents. But that is not the same as proof it would behave correctly, at scale, right now.&lt;/p&gt;

&lt;p&gt;A canary gate makes you prove it again. Every time.&lt;/p&gt;

&lt;p&gt;Apply the fix to a small slice first. One instance, maybe two. Give it real time, not theoretical time. Watch not just the metric you were chasing but the adjacent ones. The downstream ones. The ones that do not show up in the obvious dashboard. When the canary comes back clean, let the loop go wide.&lt;/p&gt;

&lt;p&gt;It adds seconds. At most minutes. It does not add more time than a human incident response. What it removes is the ability to be fast in the wrong direction.&lt;/p&gt;




&lt;p&gt;The redesign took two days. Not because canary gating is hard, but because you have to think carefully about what "watching" actually means for your system. Which signals matter for this specific remediation? How long does the canary have to hold before you trust it? How small does the slice need to be to contain the blast if the fix turns out to be wrong again?&lt;/p&gt;

&lt;p&gt;Those are not universal answers. They are specific to your failure modes, your topology, your acceptable risk. We got some of them wrong the first time around. That is fine. Getting the canary gate tuned incorrectly costs you a delayed fix or a false alarm. Getting the loop wrong without a canary gate costs you a six-hour incident and a postmortem you will write at sunrise.&lt;/p&gt;

&lt;p&gt;One of those failures fits in a sprint. The other one stays with you.&lt;/p&gt;




&lt;p&gt;Speed and confidence are not the same thing.&lt;/p&gt;

&lt;p&gt;A self-heal loop is fast. That is the point. But fast without observation is not confidence, it is just velocity. There is a real difference between a system that heals quickly because it verified the fix worked on a small slice first, and a system that heals quickly because it never stopped to check.&lt;/p&gt;

&lt;p&gt;Build the loop. Automate the healing. Let the machine take the 1:47 AM alert. But give it a gate. Make it prove the fix before it goes wide. Make it watch a small piece of the world first and tell you the world is still okay. Only then let it run.&lt;/p&gt;

&lt;p&gt;The machine should be faster than a human. It does not need to be less careful than one.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>automation</category>
      <category>devops</category>
      <category>sre</category>
    </item>
    <item>
      <title>Why We Skipped Packaging for Production</title>
      <dc:creator>praveenlavu</dc:creator>
      <pubDate>Fri, 21 Aug 2026 04:10:55 +0000</pubDate>
      <link>https://dev.to/praveenlavu/why-we-skipped-packaging-for-production-5gi4</link>
      <guid>https://dev.to/praveenlavu/why-we-skipped-packaging-for-production-5gi4</guid>
      <description>&lt;h1&gt;
  
  
  Why We Source-Deployed a Production Salesforce App Before Packaging It
&lt;/h1&gt;

&lt;p&gt;There is a question that comes up the moment you are about to ship a Salesforce app to a real customer org. Not a sandbox. Not a scratch org for a demo. A real production environment with real users who are going to depend on this thing every single day. The question sounds responsible. It sounds like the mature, professional path. "Should we package this before we go live?"&lt;/p&gt;

&lt;p&gt;We said no. And I am more confident in that call now than I was when we made it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What packaging actually commits you to
&lt;/h2&gt;

&lt;p&gt;Second-generation managed packaging in Salesforce is genuinely impressive platform engineering. You get namespace isolation, subscriber org management, controlled upgrade paths, AppExchange eligibility, and real version control over what you ship. For a stable product you plan to distribute across many orgs, it is the right architecture.&lt;/p&gt;

&lt;p&gt;But the part that does not get talked about enough is what managed packaging commits you to permanently. Every field you include in a released managed package is a field you cannot delete from subscriber orgs. Every object in your schema is a promise. The platform enforces this through its deprecation policy, and it enforces it for good reason: customers build automations, reports, integrations, and entire workflows on top of your metadata. If you yank a field, you break things they built. So Salesforce makes it structurally difficult to change your mind.&lt;/p&gt;

&lt;p&gt;That permanence is a feature when your product is mature and your schema is settled. It is a serious constraint when you are still figuring out what the product actually needs to be.&lt;/p&gt;

&lt;p&gt;When we looked at our app before the first production deployment, we knew the core concept was right. We did not know if the data model was right. That distinction matters more than most people admit early in a product's life. The architecture you design before production contact and the architecture production actually demands are almost always different. Sometimes the gap is small. Sometimes it reshapes entire sections of the schema.&lt;/p&gt;

&lt;p&gt;The honest question was not "should we package?" It was "do we know enough yet to commit to a schema we cannot change without accruing technical debt that outlives the lesson?" The answer was no. So we did not package.&lt;/p&gt;

&lt;h2&gt;
  
  
  What production actually teaches you
&lt;/h2&gt;

&lt;p&gt;We deployed via source. Direct metadata push to a real org, running against real data volumes, real user workflows, and real edge cases that no sandbox had surfaced.&lt;/p&gt;

&lt;p&gt;The first stretch of production operation was intense in a specific way. Not broken. The app worked. But we kept discovering things that changed our understanding of what the product needed to be. A field we built to carry an important signal turned out to be noise that nobody filled in consistently. A relationship we modeled one way turned out to need different ownership semantics because of how the data actually moved through the workflow. A capability we considered secondary became the one users reached for constantly.&lt;/p&gt;

&lt;p&gt;Each of these discoveries was a schema change. In source deployment, a schema change is a refactor you can execute and redeploy in a matter of hours. You update the metadata, push, handle the data migration if needed, and move on. The org stays clean. The team stays fast. The feedback loop stays tight.&lt;/p&gt;

&lt;p&gt;If we had been in a managed package, every one of those changes would have opened a negotiation. Do we mark this field deprecated and introduce a replacement? Do we cut a new package version? Do we hold the change until there are enough changes to justify the version overhead? The cognitive load of managing that process is real. The delay compounds. And the debris of deprecated fields kept alive for backward compatibility has a way of turning your schema into an archaeological site.&lt;/p&gt;

&lt;p&gt;The ability to keep learning without accumulating debt was not a nice-to-have. It was the foundation that made the product better than it would have been if we had locked the schema before production taught us anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The discipline this path actually requires
&lt;/h2&gt;

&lt;p&gt;I want to say something clearly, because source deployment can sound like permission to be loose about change control. It is not.&lt;/p&gt;

&lt;p&gt;When you take away the packaging constraint, you have to supply the discipline yourself. Every schema change goes through review. Every modification gets documented and communicated. The deployment pipeline does not do this for you the way a package version process would. The accountability is different. Not to a deprecation policy enforced by the platform, but to the users in that org whose daily work depends on you not breaking anything they depend on.&lt;/p&gt;

&lt;p&gt;That responsibility felt appropriate for where we were. The constraint we needed in that stage was not the packaging constraint. It was the production constraint. Real users creating real records, every day, with real consequences when things behaved unexpectedly. That is a sharpening force that no sandbox replicates.&lt;/p&gt;

&lt;p&gt;There were moments late at night where a change that looked clean in testing revealed an edge case under real data conditions. You trace it back, find the assumption that was wrong, fix it, redeploy, and wait to see it hold. The focus in those moments is different from anything you feel in a development environment. The stakes are different and the clarity that comes from that is different too.&lt;/p&gt;

&lt;p&gt;That is not a pleasant experience to seek out. But it is an honest one. And it made the product more solid than if we had been insulated from production feedback by packaging overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packaging when it is actually time
&lt;/h2&gt;

&lt;p&gt;None of this is an argument against packaging. Second-generation managed packaging is the right architecture for what we are building toward. The goal has always been a distributable product that can live in multiple orgs, not a single-org custom build.&lt;/p&gt;

&lt;p&gt;But the sequence matters more than most conversations about packaging acknowledge.&lt;/p&gt;

&lt;p&gt;You package when you know what you are committing to. You package when the schema is stable enough that the deprecation policy becomes a feature you want because it protects your customers rather than a constraint that would have trapped you six months earlier. You package when production has finished teaching you what the data model actually needs to hold.&lt;/p&gt;

&lt;p&gt;This is not a Salesforce-specific principle. Every platform has an equivalent version of it. You do not freeze your API contract until the API has survived real use. You do not publish a library until the interfaces are stable. You do not lock a schema until production has told you what the schema actually needs to be.&lt;/p&gt;

&lt;p&gt;The temptation to package early is understandable. It feels like the responsible, disciplined path. It looks more mature from the outside. Sometimes it is exactly right. But when your schema is still in active conversation with production reality, packaging is discipline applied to the wrong problem at the wrong time.&lt;/p&gt;

&lt;p&gt;Ship when the product is ready. Package when the contract is ready. They are not the same milestone. The engineering judgment that matters is knowing which one you are actually at, even when the responsible-sounding answer is pushing you toward the other one.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>devops</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>How I Crashed My Machine Running AI Workloads</title>
      <dc:creator>praveenlavu</dc:creator>
      <pubDate>Fri, 21 Aug 2026 04:10:24 +0000</pubDate>
      <link>https://dev.to/praveenlavu/how-i-crashed-my-machine-running-ai-workloads-1p66</link>
      <guid>https://dev.to/praveenlavu/how-i-crashed-my-machine-running-ai-workloads-1p66</guid>
      <description>&lt;h1&gt;
  
  
  When Everything Is Running, Nothing Survives
&lt;/h1&gt;

&lt;p&gt;I knew something was wrong before the screen froze.&lt;/p&gt;

&lt;p&gt;The fans had been spinning at a pitch I only hear during video rendering. The cursor was moving in that slow, sticky way it moves when the OS is losing the argument with RAM. And I had that particular feeling, the one that comes right before you realize you cannot stop what is about to happen.&lt;/p&gt;

&lt;p&gt;Then the kernel panic. Black screen. The kind of crash that does not give you a stack trace or an error log. It just stops.&lt;/p&gt;

&lt;p&gt;This was a morning in May. I had been building hard. A local AI inference model was loading into memory, a big one, the kind that takes minutes and several gigabytes before it is ready to take a single query. At the same time, I had dispatched a parallel fleet of cloud API calls to run a review sweep across several documents. Alongside that, a full automated test suite was running across the codebase I had just modified. And layered underneath all of it, an orchestration system was doing its own background processing, checking on jobs, routing work, keeping state.&lt;/p&gt;

&lt;p&gt;Each task, in isolation, was reasonable. Each one would have finished without incident if it had been the only thing running. I had done every one of them before. But I had never done them simultaneously, and on that morning I learned exactly why that was a bad idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mistake That Looks Reasonable Until It Isn't
&lt;/h2&gt;

&lt;p&gt;The mental model most of us carry about resource contention goes something like this: if one task claims half the memory and another claims half, the math should work out. A little tight, but fine.&lt;/p&gt;

&lt;p&gt;That model is wrong the moment you mix certain categories of work.&lt;/p&gt;

&lt;p&gt;The crash forced me to sit with a question I had been avoiding: what, exactly, is the fundamental difference between the kinds of work I am doing? Not by domain or purpose, but by the kind of load they impose on the machine.&lt;/p&gt;

&lt;p&gt;I spent the day after the crash reconstructing what had actually happened. The local model load was RAM-intensive and held the resources it claimed for an extended period. The cloud API fleet was network-bound and spawned process threads that competed for CPU scheduling. The test sweep was both I/O-intensive and CPU-intensive, constantly reading files and computing results. The orchestration daemon needed consistent access to disk and memory for its state.&lt;/p&gt;

&lt;p&gt;None of these played well together. But more importantly, I had mixed two categories of work that should never share a time window: local-heavy tasks that demand a machine almost entirely to themselves, and distributed tasks that can run in bounded parallel because their weight falls on remote infrastructure.&lt;/p&gt;

&lt;p&gt;The kernel panic was not a bug. It was the machine telling me I had misunderstood my own workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Protocol That Came Out of the Wreckage
&lt;/h2&gt;

&lt;p&gt;A crash of that kind tends to produce either frustration or clarity. I have learned to aim for clarity.&lt;/p&gt;

&lt;p&gt;The clarification I wrote for myself that afternoon was about resource classes. Some tasks are local-heavy. They need to run alone, serially, with the machine's full attention, because they compete with everything else for RAM, CPU, and disk. Loading a large inference model. Running a full test sweep. Triggering a heavy orchestration job outside its scheduled window. These things are incompatible with each other and with anything else that demands significant resources at the same time.&lt;/p&gt;

&lt;p&gt;Other tasks are what I started calling remote-fleet work. They dispatch to external APIs and wait for responses. The actual computation happens elsewhere. These can run in bounded parallel, not unlimited, but several at once, because the local machine is mostly waiting, not working.&lt;/p&gt;

&lt;p&gt;The rule I came up with was simple: never cross the classes. If a local-heavy task is active, no fleet work starts. If fleet work is active, no local-heavy task starts. And before any heavy task begins, I now check three things: CPU load over the last minute, available disk space, and whether any other heavy task is in flight.&lt;/p&gt;

&lt;p&gt;That last check sounds obvious in retrospect. It was not obvious when I was in the middle of building something at speed and just needed all the things to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Solo Founders Carry That Teams Do Not Have To
&lt;/h2&gt;

&lt;p&gt;There is something specific about building complex systems alone that creates this kind of risk.&lt;/p&gt;

&lt;p&gt;In a team, the person loading the inference model is probably not also running the test suite. Resource contention at the infrastructure level gets distributed across people without anyone designing it that way. You share machines, or you have separate machines, or the pipeline forces serialization as a side effect of handoffs between people.&lt;/p&gt;

&lt;p&gt;Solo, you are the pipeline. Every task is yours to schedule, and the temptation, especially when you are in a state of flow, when the work is moving fast and everything feels connected, is to run everything together because everything feels connected.&lt;/p&gt;

&lt;p&gt;The discipline that fixed this for me was a forcing function: written resource classes. A short document that puts each category of heavy work in exactly one bucket, with a hard rule about cross-mixing. Once it was written down, it became visible. And once it was visible, I stopped doing the thing that crashed my machine.&lt;/p&gt;

&lt;p&gt;I still run into the temptation. The model is loading and I think, I will just kick off this API sweep while I wait. That thought is the red flag now. That is exactly the configuration that produced the 10:58 crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Thing About Postmortems Is That They Compound
&lt;/h2&gt;

&lt;p&gt;Solo founders often skip the postmortem. There is no one to write it for, and the format feels designed for teams. Who are you presenting to? Who needs to understand the incident? You already know what happened.&lt;/p&gt;

&lt;p&gt;That instinct is wrong, and I say that as someone who learned it the hard way.&lt;/p&gt;

&lt;p&gt;The value of a postmortem when you are building alone is not the audience. It is the forcing function of articulation. Writing down what happened, at the level of root cause rather than symptom, is a different cognitive act than just knowing what happened. The kernel panic gave me a clear memory of the chaos. The postmortem I wrote the next day gave me a transferable principle.&lt;/p&gt;

&lt;p&gt;Local-heavy and remote-fleet work do not mix. A preflight check before any heavy task is non-negotiable. And the temptation to cross classes is highest exactly when the work is going well and the momentum feels unstoppable.&lt;/p&gt;

&lt;p&gt;Write the postmortem. Write it for yourself. Write it while it still stings, because that is when the root cause thinking is sharpest. File it somewhere you will see it. And then when the same temptation appears three months later, you will have something to read instead of something to repeat.&lt;/p&gt;

&lt;p&gt;The machine will tell you. You just want to hear it in a document first.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>hardware</category>
      <category>linux</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Validate Your AI Judge With Negative Controls</title>
      <dc:creator>praveenlavu</dc:creator>
      <pubDate>Fri, 21 Aug 2026 04:09:52 +0000</pubDate>
      <link>https://dev.to/praveenlavu/validate-your-ai-judge-with-negative-controls-2f9</link>
      <guid>https://dev.to/praveenlavu/validate-your-ai-judge-with-negative-controls-2f9</guid>
      <description>&lt;h1&gt;
  
  
  The Test That Has to Fail
&lt;/h1&gt;

&lt;p&gt;There is a specific kind of dread that sets in when everything looks too good.&lt;/p&gt;

&lt;p&gt;I had spent weeks wiring an AI judge into the review layer of a generation pipeline.&lt;br&gt;
The judge was an LLM we had carefully prompted to read outputs and score them against&lt;br&gt;
a rubric: coherence, factual grounding, format compliance, tone. It was supposed to&lt;br&gt;
be the quality gate that meant a human did not have to eyeball every single thing the&lt;br&gt;
pipeline produced. In theory, a beautiful piece of leverage.&lt;/p&gt;

&lt;p&gt;Then one afternoon I opened the evaluation dashboard and saw green across the board.&lt;br&gt;
Every output from the last three batches: approved. Pass rate sitting right at the top.&lt;/p&gt;

&lt;p&gt;I stared at it for a long time. And instead of feeling relieved, I felt the floor&lt;br&gt;
tilt under me.&lt;/p&gt;

&lt;p&gt;Because I realized I had no idea whether the judge was working or whether it had&lt;br&gt;
quietly learned to approve everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With a Judge That Never Says No
&lt;/h2&gt;

&lt;p&gt;Here is the thing about AI judges that nobody puts in the tutorial: a judge that&lt;br&gt;
never flags anything looks exactly the same on a dashboard as a judge that is&lt;br&gt;
genuinely evaluating and finding everything acceptable. From the outside, both&lt;br&gt;
produce the same green tiles.&lt;/p&gt;

&lt;p&gt;We built the judge in the first place because human review at scale is expensive&lt;br&gt;
and slow. The pipeline was producing too much output for a person to read&lt;br&gt;
everything. So the judge was meant to stand in, catch the bad stuff, and let the&lt;br&gt;
good stuff through. Standard quality-gate logic.&lt;/p&gt;

&lt;p&gt;But quality gates only work if they can actually close.&lt;/p&gt;

&lt;p&gt;With traditional software, this is not that hard to prove. You write a unit test&lt;br&gt;
that checks a function's output. You run it before writing the function and watch&lt;br&gt;
it fail. The failure is the evidence that the test is real, that it is actually&lt;br&gt;
checking what you think it is checking. You cannot claim a test is valid until you&lt;br&gt;
have seen it fail.&lt;/p&gt;

&lt;p&gt;With an AI judge, that discipline breaks down in a frustrating way. The judge is&lt;br&gt;
not deterministic. It does not have a clean, inspectable logic path you can trace.&lt;br&gt;
It is a model that was trained on patterns and is now producing judgments. You&lt;br&gt;
cannot look inside and verify the wiring. You can only observe what it does.&lt;/p&gt;

&lt;p&gt;So you run some outputs through it and it says they are fine. But fine compared to&lt;br&gt;
what? What would have to be true about an output for the judge to flag it? You do&lt;br&gt;
not actually know. And that not-knowing is a loaded gun sitting quietly on the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Recursive Trap
&lt;/h2&gt;

&lt;p&gt;The obvious instinct at this point is to validate the judge with another judge. Run&lt;br&gt;
the judge on a set of outputs where you already know the ground truth, score the&lt;br&gt;
judge's calls against that truth, measure accuracy.&lt;/p&gt;

&lt;p&gt;The problem is: where does the ground truth come from?&lt;/p&gt;

&lt;p&gt;If a human labeled the ground truth, you now need a human to be involved, which was&lt;br&gt;
the thing you were trying to get away from. If another AI labeled the ground truth,&lt;br&gt;
you have moved the trust problem up exactly one level. The question is now whether&lt;br&gt;
you can trust the labeler, which is the same question you started with, just wearing&lt;br&gt;
a different hat.&lt;/p&gt;

&lt;p&gt;There is a version of this that works, carefully constructed human-labeled eval&lt;br&gt;
sets, regularly updated, treated as a permanent fixture of the pipeline. It is&lt;br&gt;
expensive to build and requires real discipline to maintain. Most teams skip it&lt;br&gt;
and convince themselves the all-green dashboard means things are fine.&lt;/p&gt;

&lt;p&gt;I was one of those teams. Until the dread sat with me long enough that I started&lt;br&gt;
looking for a different answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Lab Scientists Figured Out
&lt;/h2&gt;

&lt;p&gt;I had a conversation with someone who had spent years doing cell biology before&lt;br&gt;
moving into software. I was describing the judge-validation problem and she gave me&lt;br&gt;
a look like I was overcomplicating something obvious.&lt;/p&gt;

&lt;p&gt;"Negative controls," she said.&lt;/p&gt;

&lt;p&gt;In a biology lab, a negative control is a sample that is specifically prepared to&lt;br&gt;
produce no signal. You run it alongside your real samples every single time you run&lt;br&gt;
an assay. The negative control is not supposed to tell you anything about your&lt;br&gt;
samples. It is supposed to tell you whether your assay is working.&lt;/p&gt;

&lt;p&gt;If the negative control produces a positive result, something is wrong with your&lt;br&gt;
assay. Maybe your reagent is contaminated. Maybe there was a procedural error.&lt;br&gt;
Maybe the test is detecting something other than what you thought. Whatever the&lt;br&gt;
reason, a positive on a negative control is a hard stop. You do not trust any of&lt;br&gt;
the results from that run. You fix the assay first.&lt;/p&gt;

&lt;p&gt;The negative control has to fail. That is the entire point. Its failure is the&lt;br&gt;
evidence that the test is real.&lt;/p&gt;

&lt;p&gt;I wrote that down and sat with it for a while. Then I started building what I&lt;br&gt;
should have built six weeks earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fixture Library
&lt;/h2&gt;

&lt;p&gt;A negative-control fixture for an AI judge is an output that MUST be flagged.&lt;br&gt;
Not one that should probably be flagged, not one that a careful reader might&lt;br&gt;
flag. One where there is no reasonable interpretation under which the judge&lt;br&gt;
should approve it.&lt;/p&gt;

&lt;p&gt;Building the library means constructing failures deliberately. Incoherent text&lt;br&gt;
that looks like a paragraph but does not follow any logical thread. Factual claims&lt;br&gt;
that contradict themselves within two sentences. Required format sections that are&lt;br&gt;
simply missing. Responses that are technically words but are completely off-topic&lt;br&gt;
relative to the input. The kind of output that, if a human saw it, would prompt&lt;br&gt;
a baffled look and a "what is this?"&lt;/p&gt;

&lt;p&gt;The fixture library does not need to be large. It needs to cover the main failure&lt;br&gt;
categories your judge is supposed to catch, with examples that are so clearly&lt;br&gt;
wrong that any functioning judge should reject them. A dozen well-constructed&lt;br&gt;
fixtures is more valuable than a hundred borderline ones.&lt;/p&gt;

&lt;p&gt;Then you run the library at the start of every evaluation batch, before a single&lt;br&gt;
real output gets scored. If the judge passes any fixture, the batch does not run.&lt;br&gt;
You stop, diagnose the judge, fix what broke, and run the library again. Only&lt;br&gt;
when every fixture fails, only when the judge correctly rejects every piece of&lt;br&gt;
known-bad output, do you trust it with the real work.&lt;/p&gt;

&lt;p&gt;That is the discipline. And it is not glamorous. Building the fixture library&lt;br&gt;
takes a few hours and produces nothing you can show a stakeholder. It does not&lt;br&gt;
make the pipeline faster or the outputs better. It just makes the judge honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Test Suite That Never Fails Is Telling You Something
&lt;/h2&gt;

&lt;p&gt;After I added the negative-control layer, the first thing that happened was that&lt;br&gt;
the judge failed three fixtures I was confident it would catch. The failures were&lt;br&gt;
in a category I had not thought to tune for. I spent a day fixing the judge's&lt;br&gt;
prompt, rerunning, failing, adjusting again. Uncomfortable. Slow. Exactly what I&lt;br&gt;
did not want to spend time on.&lt;/p&gt;

&lt;p&gt;But here is what changed: when the dashboard finally went green after that, I&lt;br&gt;
actually believed it.&lt;/p&gt;

&lt;p&gt;The principle I took away is one that applies anywhere you are testing something&lt;br&gt;
that does not have a deterministic, inspectable core. A test suite that never&lt;br&gt;
fails is not evidence of quality. It is evidence of blindness. Either the tests&lt;br&gt;
are not checking what you think they are checking, or the system has found a way&lt;br&gt;
to pass them without doing the real work, or both.&lt;/p&gt;

&lt;p&gt;The only honest quality gate is one that you have personally seen close.&lt;/p&gt;

&lt;p&gt;If you are building AI evaluation into a pipeline and you have not yet built the&lt;br&gt;
fixtures designed to make the judge fail, the all-green dashboard is not your&lt;br&gt;
friend. It is just the absence of evidence pretending to be evidence of absence.&lt;/p&gt;

&lt;p&gt;Build the tests that have to fail. Run them first. Trust the rest only after.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
    </item>
    <item>
      <title>The License Blind Spot in AI Orchestration</title>
      <dc:creator>praveenlavu</dc:creator>
      <pubDate>Fri, 21 Aug 2026 04:09:21 +0000</pubDate>
      <link>https://dev.to/praveenlavu/the-license-blind-spot-in-ai-orchestration-54mk</link>
      <guid>https://dev.to/praveenlavu/the-license-blind-spot-in-ai-orchestration-54mk</guid>
      <description>&lt;h1&gt;
  
  
  The Router Didn't Know the Rules
&lt;/h1&gt;

&lt;p&gt;There is a specific kind of dread that arrives when you realize a system you built has been doing something wrong the whole time. Not crashing. Not throwing errors. Just quietly, confidently, doing the wrong thing. I hit that moment while reviewing routing logs late one evening.&lt;/p&gt;

&lt;p&gt;The orchestrator had been working well. Tasks were going to the right agents, local models were handling bulk inference cheaply and fast, cloud models stepping in when the task demanded it. Every metric was green. But something nagged at me, so I pulled a few routing traces and started reading them carefully.&lt;/p&gt;

&lt;p&gt;The routing logic picked the best available model by capability score. Lightweight models for quick classification. More capable local models for longer reasoning chains. The logic was sound. The implementation was clean. And then I saw the gap: nowhere in that logic did I track what licenses those local models were running under.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Blind Spot Every Routing Layer Carries
&lt;/h2&gt;

&lt;p&gt;When you build an orchestration layer, you spend most of your time thinking about capability. Which model handles long context well? Which one costs less per token? Which one is fastest on short tasks? You build scoring systems, run comparisons, set latency budgets. It feels thorough.&lt;/p&gt;

&lt;p&gt;What you do not naturally account for is that model licenses are not uniform. Open weights does not mean free for any use. Many of the best local models, the ones that make self-hosted inference genuinely compelling, carry non-commercial licenses. They are excellent for research, internal exploration, evaluation pipelines. They are not permitted for commercial workflows.&lt;/p&gt;

&lt;p&gt;An orchestration system that does not encode this distinction routes freely between commercial and non-commercial models with zero awareness of the difference. The router optimizes for the metric it was given. License compliance was not a metric. So it did not exist in the router's world.&lt;/p&gt;

&lt;p&gt;I had built a system that was, under certain conditions, routing commercial work through models that explicitly prohibit it. Not out of carelessness. Because I had never made license type a routing signal at all. The omission was invisible until I looked for it, and that invisibility is exactly what made it dangerous.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Takes to Fix It Properly
&lt;/h2&gt;

&lt;p&gt;The instinct is to treat this as a filter. Just screen out non-commercial models when the work context is commercial. That works, barely. It also misses the real engineering opportunity.&lt;/p&gt;

&lt;p&gt;License type is not just a constraint to honor. It is a signal that restructures the routing decision from the start. A non-commercial model can be exactly the right tool for a research workload, internal tooling, or a test harness. A commercial model is the appropriate default for anything touching revenue or client deliverables, not because it is necessarily more capable, but because its use is unambiguously permitted.&lt;/p&gt;

&lt;p&gt;What I built was a license gate that runs before capability scoring begins. The routing logic stops asking what the best model is for this task. It starts by asking what the eligible set of models is for this work context. Only after that question is answered does capability scoring enter the picture.&lt;/p&gt;

&lt;p&gt;This is a meaningful shift. The gate is not bolted to the end of a routing pipeline. It runs first. Before latency budgets, before cost optimization, before any capability comparison, the work context is classified and the model's license class is checked. The routing decision then proceeds on the eligible set only.&lt;/p&gt;

&lt;p&gt;The work-context classification turned out to be the hard part. Whether a task is commercial work sounds like an easy question. In practice, that answer lives inside the routing context: which team submitted the request, what project it belongs to, whether the path is test infrastructure or a production route. Getting that signal propagated cleanly to the routing layer required treating it as a first-class field in the task envelope, not something inferred after the fact from other signals.&lt;/p&gt;

&lt;p&gt;The model side required building a license registry alongside the existing capability registry. Every model in the fleet carries a declared license class. The gate reads from both registries at routing time. Neither is more authoritative than the other. They answer different questions, and both questions matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Part I Did Not Expect
&lt;/h2&gt;

&lt;p&gt;Here is what surprised me: once license was a first-class routing signal, the system became more useful, not just more compliant.&lt;/p&gt;

&lt;p&gt;Routing to a local non-commercial model for internal research became an explicit, auditable decision rather than an optimization artifact. You could look at any routing trace and understand not just which model ran, but why it was eligible to run. The license gate created a clear boundary between two classes of inference, and that boundary turned out to be a useful interface for thinking about cost allocation, data privacy, and model governance all at once.&lt;/p&gt;

&lt;p&gt;Non-commercial models are, in many situations, the right tool for internal work. They run fast, they stay on-premises, they do not bill per token. An orchestration system that can confidently route internal workloads to them, because it has verified the license is clean for that context, is more efficient than one that defaults everything to commercial inference out of caution. The gate did not constrain the system. It gave the system clear knowledge of what it was allowed to do, and that clarity opened better decisions in every direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Principle
&lt;/h2&gt;

&lt;p&gt;License compliance in AI orchestration is not a legal checkbox bolted on after the system is built. It is a routing constraint that belongs at the foundation of any system mixing model tiers.&lt;/p&gt;

&lt;p&gt;The orchestration layer knows things the application layer never sees: which model ran, what it cost, how long it took. It should also know whether that model was permitted to run, for this work, in this context. That knowledge, encoded correctly, makes the whole system more trustworthy and easier to audit, not just for compliance but for anyone trying to understand what the system actually did and why.&lt;/p&gt;

&lt;p&gt;If you are building a routing layer that touches local models, ask this question early: does the router know what it is allowed to do? Not what it is capable of doing. What it is permitted to do, for this specific work, in this specific context. Building that distinction into the routing foundation is far cheaper than discovering its absence later in a log review at midnight.&lt;/p&gt;

&lt;p&gt;The router should always know the rules it is routing under. Working that out at the architecture level, rather than the compliance level, is the move that actually sticks.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
