<?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: Venkatesh S</title>
    <description>The latest articles on DEV Community by Venkatesh S (@venkathub).</description>
    <link>https://dev.to/venkathub</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%2F4040816%2F9108a1d4-8af9-4930-b195-0342de2710f6.jpg</url>
      <title>DEV Community: Venkatesh S</title>
      <link>https://dev.to/venkathub</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/venkathub"/>
    <language>en</language>
    <item>
      <title>From Java Monoliths to LangGraph Agents: What Transfers</title>
      <dc:creator>Venkatesh S</dc:creator>
      <pubDate>Mon, 27 Jul 2026 08:40:38 +0000</pubDate>
      <link>https://dev.to/venkathub/from-java-monoliths-to-langgraph-agents-what-transfers-2l37</link>
      <guid>https://dev.to/venkathub/from-java-monoliths-to-langgraph-agents-what-transfers-2l37</guid>
      <description>&lt;p&gt;I spent ten years building enterprise Java systems — a payments monolith broken into Spring Cloud microservices, banking applications, and currently a Cisco security platform. More recently I moved into building LLM systems seriously. The surprise wasn't how much I had to learn. It was how much transferred.&lt;/p&gt;

&lt;p&gt;Agentic systems are distributed systems. The vocabulary is new; the failure modes are not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Timeouts and retries — same problem, worse defaults
&lt;/h2&gt;

&lt;p&gt;A Java service that calls a flaky dependency without timeouts is a junior-engineer mistake. An agent that calls an LLM without them is somehow normal. LLM calls are slow, occasionally hang, and fail in novel ways (malformed JSON, refusals, half-finished tool calls). Everything Resilience4j taught me applies directly: bounded retries with backoff, circuit breakers that fail fast with an honest error instead of silently substituting something expensive, and — this one is new — &lt;em&gt;semantic&lt;/em&gt; failure detection, because an LLM can return HTTP 200 with garbage inside.&lt;/p&gt;

&lt;p&gt;In Atlas, the gateway circuit-breaks to &lt;code&gt;503 + Retry-After&lt;/code&gt; when the model endpoint is down. That is deliberately boring. Boring is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotency — now with side effects an LLM chose
&lt;/h2&gt;

&lt;p&gt;Backend rule: any handler that can be retried must be idempotent. Agent rule: same, except the retry might be the &lt;em&gt;planner&lt;/em&gt; deciding to call the tool again because the first response "didn't look right." If your agent's tools mutate state, you need idempotency keys and an audit trail more than a chat UI needs streaming.&lt;/p&gt;

&lt;p&gt;Atlas routes every write through an MCP tool server with audience-scoped tokens (RFC 8707) and an append-only, hash-chained audit log, behind a human-in-the-loop checkpoint for governed actions. That design is not AI innovation — it's what any bank-grade action API looks like. The innovation is refusing to drop the standard just because an LLM is the caller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost budgets are the new capacity planning
&lt;/h2&gt;

&lt;p&gt;In backend work you plan capacity: connection pools, thread pools, rate limits. In LLM systems the scarce resource is spend. The same machinery applies — token-bucket rate limiting, a per-user daily budget guard (in Atlas: Redis-backed, keyed per user per UTC day), alerting at a spend ceiling — plus one new lever: &lt;strong&gt;routing&lt;/strong&gt;. Atlas routes simple queries to a small model and reserves larger models for multi-step reasoning, the way you'd route reads to a replica. Cost regression is gated in CI like a latency budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  State machines beat vibes
&lt;/h2&gt;

&lt;p&gt;LangGraph clicked for me precisely because it &lt;em&gt;isn't&lt;/em&gt; magic: it's an explicit state graph with checkpoints — nodes, edges, persisted state, resumability. If you've designed a workflow engine or a saga with compensation steps, you already think this way. The planner–executor pattern with a durable human-approval checkpoint is a state machine an enterprise architect would recognize from a 2010 BPM diagram. That's a compliment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's genuinely new
&lt;/h2&gt;

&lt;p&gt;Honesty requires the other list. Three things had no backend equivalent and took real work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation is statistical, not binary.&lt;/strong&gt; Tests pass or fail; eval metrics move in distributions. Learning to set floors and regression bands (and when to reach for paired bootstrap and McNemar) was new muscle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The retrieval layer is a ranking problem.&lt;/strong&gt; Hybrid search, reranking, and abstention thresholds are closer to information retrieval than to anything in Spring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt injection inverts the trust model.&lt;/strong&gt; Backend security trusts your own data; RAG security cannot — retrieved documents are untrusted input &lt;em&gt;to the model&lt;/em&gt;. Spotlighting and quarantine have no REST-era analogue.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The pitch, condensed
&lt;/h2&gt;

&lt;p&gt;If you're a backend engineer eyeing AI engineering: your instincts about timeouts, idempotency, budgets, audit, and state are not legacy baggage — they're the exact discipline most LLM systems are missing. Learn retrieval, learn evals, keep the discipline.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>java</category>
      <category>career</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Permission-Aware RAG: Enforcing Document ACLs at Retrieval Time</title>
      <dc:creator>Venkatesh S</dc:creator>
      <pubDate>Mon, 27 Jul 2026 08:40:19 +0000</pubDate>
      <link>https://dev.to/venkathub/permission-aware-rag-enforcing-document-acls-at-retrieval-time-54a7</link>
      <guid>https://dev.to/venkathub/permission-aware-rag-enforcing-document-acls-at-retrieval-time-54a7</guid>
      <description>&lt;p&gt;Most RAG demos have a security model of "none." Documents go into one shared index; anyone who can ask a question can surface content from any document. In a compliance or financial domain, that's not a rough edge — it's a disqualifier.&lt;/p&gt;

&lt;p&gt;When I built Atlas, an enterprise copilot for a compliance domain, the first architectural decision was where access control lives. There are three options, and two of them are wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1 (wrong): filter after generation
&lt;/h2&gt;

&lt;p&gt;Generate the answer, then check whether the user was allowed to see the sources. By then the LLM has already read the restricted content and may have leaked it through paraphrase, summary, or even its refusal ("I can't tell you about the Q3 restructuring memo…" is itself a leak). Post-hoc filtering treats a security boundary like a UX problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2 (wrong): filter the final chunk list
&lt;/h2&gt;

&lt;p&gt;Retrieve top-k from the shared index, then drop chunks the user can't access. Better, but it corrupts retrieval quality: your top-k gets silently thinned, and ranking scores were computed against a corpus the user shouldn't see. Worse, timing and scoring side-channels can reveal that restricted documents exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 3: make clearance part of the retrieval predicate
&lt;/h2&gt;

&lt;p&gt;In Atlas, every chunk in pgvector carries a clearance label (&lt;code&gt;public | analyst | compliance | restricted&lt;/code&gt;) as metadata, written at ingestion. Retrieval filters by the caller's &lt;em&gt;verified&lt;/em&gt; clearance &lt;strong&gt;before&lt;/strong&gt; ranking. This is the actual shape of the dense retrieval query (sparse tsvector search shares the same predicate builder):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clearance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;atlas_chunk&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;clearance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;-- caller's visible labels, enforced BEFORE ranking&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One detail worth copying: both retrieval paths — dense kNN and sparse full-text — build their &lt;code&gt;WHERE&lt;/code&gt; clause from a &lt;strong&gt;single shared RBAC filter builder&lt;/strong&gt;, so there is exactly one trust boundary and it cannot be bypassed. The predicate is pushed into SQL, never applied as an optional post-filter.&lt;/p&gt;

&lt;p&gt;The property this buys you: cross-clearance leakage becomes &lt;em&gt;structurally impossible&lt;/em&gt;, not "tested for." The restricted document isn't ranked lower or filtered out — from the query's perspective, it does not exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part everyone skips: proving it stays true
&lt;/h2&gt;

&lt;p&gt;A design is worth little without a regression gate. Atlas has a &lt;strong&gt;negative-access hard gate&lt;/strong&gt; in CI: an eval suite where low-clearance users ask questions whose only correct sources are restricted documents. The passing behavior is a grounded refusal — and the gate fails the build on any leakage. It runs GPU-free against committed cassettes, so it gates every merge like a unit test.&lt;/p&gt;

&lt;p&gt;Two implementation notes that mattered:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clearance comes from the verified identity, not the request.&lt;/strong&gt; The gateway asserts an independently-verified internal JWT; the RAG engine resolves clearance from that assertion and ignores any client-supplied clearance header when a valid assertion is present. (An attacker who can pass &lt;code&gt;clearance=SECRET&lt;/code&gt; as a parameter owns your corpus.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The semantic cache is clearance-partitioned.&lt;/strong&gt; A cached answer computed for a high-clearance user must never be served to a low-clearance one. Cache keys include the clearance tier — a leak path that's easy to miss because caching is "just performance."&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Ten years of backend work taught me that authorization belongs in the data path, not bolted on after. RAG doesn't change that rule; it just gives you a new data path to forget it in. Put the ACL in the retrieval predicate, then write the eval that fails your build when someone breaks it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>security</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Eval-Gated AI Releases: Treating Retrieval Quality Like Unit Tests</title>
      <dc:creator>Venkatesh S</dc:creator>
      <pubDate>Tue, 21 Jul 2026 22:42:49 +0000</pubDate>
      <link>https://dev.to/venkathub/eval-gated-ai-releases-treating-retrieval-quality-like-unit-tests-4o9j</link>
      <guid>https://dev.to/venkathub/eval-gated-ai-releases-treating-retrieval-quality-like-unit-tests-4o9j</guid>
      <description>&lt;p&gt;No backend team would merge a PR that fails the test suite. Yet many AI teams ship prompt and model changes with no automated quality check at all — they eyeball a few responses and hope. The failure mode is silent: answers get slightly less faithful, citations drift, and nobody notices until a user does.&lt;/p&gt;

&lt;p&gt;Atlas (my enterprise RAG copilot project) treats evals exactly like tests: &lt;strong&gt;a merge is blocked on a quality or cost regression.&lt;/strong&gt; Here's the shape of that pipeline and what I learned building it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gates the build
&lt;/h2&gt;

&lt;p&gt;Every PR runs an offline, deterministic, GPU-free eval gate against committed cassettes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Baseline&lt;/th&gt;
&lt;th&gt;Floor&lt;/th&gt;
&lt;th&gt;Policy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RAGAS faithfulness&lt;/td&gt;
&lt;td&gt;0.706&lt;/td&gt;
&lt;td&gt;0.656&lt;/td&gt;
&lt;td&gt;blocking, regression band 0.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Answer relevancy&lt;/td&gt;
&lt;td&gt;0.832&lt;/td&gt;
&lt;td&gt;0.782&lt;/td&gt;
&lt;td&gt;blocking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context recall&lt;/td&gt;
&lt;td&gt;0.738&lt;/td&gt;
&lt;td&gt;0.668&lt;/td&gt;
&lt;td&gt;blocking, band 0.07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Citation correctness&lt;/td&gt;
&lt;td&gt;1.000&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;tracked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adversarial suite (injection)&lt;/td&gt;
&lt;td&gt;100% pass&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;zero-tolerance, blocking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per request&lt;/td&gt;
&lt;td&gt;committed baseline&lt;/td&gt;
&lt;td&gt;reduction target&lt;/td&gt;
&lt;td&gt;blocking (separate cost gate)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three design decisions made this workable:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Floors + regression bands, not perfection.&lt;/strong&gt; A hard "faithfulness ≥ 0.9" gate would have blocked every merge forever. Instead: an absolute floor (never ship below this) plus a regression band relative to the committed baseline (never get meaningfully worse). This is the eval equivalent of ratcheting code coverage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Deterministic and GPU-free in CI.&lt;/strong&gt; Live-model evals in CI are slow, expensive, and flaky. Atlas replays committed cassettes — recorded model interactions — so the gate is fast and reproducible. Cassettes are re-recorded deliberately when behavior is supposed to change, which turns "the model changed" into a reviewed diff instead of an ambient surprise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Cost is a gated metric, not a dashboard.&lt;/strong&gt; A prompt change that doubles token usage is a regression even if quality holds. Atlas's cost gate asserts the measured cost-reduction against a committed baseline and fails the build when cost regresses below the band — the same way a p95-latency budget would gate a backend service.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payoff: decisions you can defend
&lt;/h2&gt;

&lt;p&gt;The gate earned its keep when I fine-tuned a QLoRA adapter for citation formatting. The benchmark (paired bootstrap, McNemar) showed: citation format validity 0.00 → 0.955 (p ≈ 0), serving cost −79% on the same GPU — and a faithfulness trade-off of −0.10, still above the floor. The promotion gate had the data to make that call explicitly, and the pipeline is proven to both promote &lt;em&gt;and&lt;/em&gt; block a candidate in CI.&lt;/p&gt;

&lt;p&gt;Without evals, that's a vibes decision. With them, it's an engineering decision with a paper trail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start (smaller than you think)
&lt;/h2&gt;

&lt;p&gt;You don't need RAGAS on day one. Start with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;20–30 golden questions&lt;/strong&gt; with expected source documents — your retrieval hit-rate suite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A handful of adversarial prompts&lt;/strong&gt; (injection attempts, out-of-scope questions) with expected refusals.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A CI job that fails when either regresses.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's an afternoon of work, and it changes the culture immediately: prompts become code, model swaps become reviewed changes, and "did we get worse?" has an answer.&lt;/p&gt;

&lt;p&gt;Backend engineering solved this problem twenty years ago — we just called it testing. AI systems don't need a new philosophy; they need the old one applied.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
