<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Data Reply IT | DataTech - Medium]]></title>
        <description><![CDATA[Data Reply is the Reply group company that offers excellent services on Big Data, Artificial Intelligence and Quantum Computing. We operate across different industries and business functions and we support C-levels in extracting the real value present in corporate data. - Medium]]></description>
        <link>https://medium.com/data-reply-it-datatech?source=rss----1431aad73386---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Data Reply IT | DataTech - Medium</title>
            <link>https://medium.com/data-reply-it-datatech?source=rss----1431aad73386---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 10 Nov 2026 05:51:15 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/data-reply-it-datatech" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Data Contracts Are Not Documentation: How to Stop Breaking Changes Before They Reach Production]]></title>
            <link>https://medium.com/data-reply-it-datatech/data-contracts-are-not-documentation-how-to-stop-breaking-changes-before-they-reach-production-7b067b192247?source=rss----1431aad73386---4</link>
            <guid isPermaLink="false">https://medium.com/p/7b067b192247</guid>
            <category><![CDATA[data-engineering]]></category>
            <category><![CDATA[data-contract]]></category>
            <category><![CDATA[databricks]]></category>
            <category><![CDATA[data-quality]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[Manuel Fasanella]]></dc:creator>
            <pubDate>Wed, 04 Nov 2026 08:01:01 GMT</pubDate>
            <atom:updated>2026-11-04T08:01:01.757Z</atom:updated>
            <content:encoded><![CDATA[<p><em>A practical guide to turning implicit data expectations into versioned, testable, and enforceable agreements, from the pull request to the pipeline</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-Z-b8TnTYrjv2xvUalz05g.png" /><figcaption><em>A data contract connects producers, validation rules, and consumers.</em></figcaption></figure><p>It is Monday, 8:07 AM. The CFO opens the weekly revenue dashboard and sees a 38% drop compared to last week. By 8:15, three Slack channels are on fire. By 9:00, analysts are comparing screenshots, the data science team is asking why the forecasting model went crazy overnight, and the data engineering team is digging through pipeline logs that show… nothing. Every job is green.</p><p>The culprit is found at 11:30. Over the weekend, the commerce team released a new version of the checkout service and, as part of a refactoring, renamed the order status PAID to COMPLETED. From their point of view, it was a perfectly reasonable change: cleaner code, green tests. Nobody told them that a revenue dashboard filtered on order_status IN (&#39;PAID&#39;, &#39;SHIPPED&#39;).</p><p>Nothing was <em>technically</em> broken. Same schema, same types, successful pipeline. And yet the data was wrong.</p><p>The painful part is not that data changed. Data <em>must</em> change, because products, customers, and businesses change. The painful part is that the change arrived as a <strong>surprise</strong>.</p><p>This is exactly the problem <strong>data contracts</strong> are designed to solve, but not in the way many teams implement them. A data contract is not a PDF in Confluence that nobody reads after the first week. <strong>A data contract is a process</strong>: a repeatable, automated way for producers and consumers to agree on what the data means, what “good” looks like, who owns it, and how changes are introduced safely.</p><p>In this guide, we will explore how to design data contracts with the <strong>Open Data Contract Standard (ODCS)</strong>, catch breaking changes directly in pull requests, and enforce the contract at runtime with Databricks Lakeflow pipelines. Then we will see how to roll it all out without making your teams revolt.</p><h3>The Real Problem: Implicit Assumptions</h3><p>Most data incidents are not caused by an absence of validation. They are caused by <strong>implicit expectations</strong>, rules that everyone relies on but nobody ever wrote down:</p><ul><li>“This field is always present.”</li><li>“This ID is unique.”</li><li>“This timestamp is in UTC.”</li><li>“This table lands by 8:00 AM.”</li><li>“These are the only possible values of order_status.&quot;</li></ul><p>When these expectations live only in people’s heads, or scattered across notebooks and SQL filters, every change becomes a gamble. The producer cannot know who depends on what, and the consumer cannot know when something is about to change.</p><p>Software engineering solved this problem for APIs a long time ago. Nobody would ship a breaking change to a public REST API without versioning, a changelog, and a deprecation period. Yet we routinely allow the tables that feed finance reports and ML models to change without any of these guarantees.</p><p><strong>Data contracts bring the discipline of API design to data.</strong></p><h3>From Documentation to Process</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cbNYFo3IZkO0IwCChkDw8A.png" /><figcaption><em>Contract changes move through review, validation, and release.</em></figcaption></figure><p>A contract is not a one-time artifact. It is a workflow that must answer six questions:</p><ol><li><strong>Definition</strong>: what is this dataset, and what do its fields mean?</li><li><strong>Expectations</strong>: which rules must always hold?</li><li><strong>Ownership</strong>: who is responsible for the data, and who gets paged when it breaks?</li><li><strong>Change management</strong>: how are changes versioned and communicated?</li><li><strong>Enforcement</strong>: where are the rules evaluated, and what happens on a violation?</li><li><strong>Observability</strong>: how do we know the contract is actually improving reliability?</li></ol><p>Here is the litmus test: <strong>if your data contract does not change the behavior of your CI/CD, your pipelines, and your incident response, it is just documentation with a new name.</strong></p><p>This is also what <em>shift-left</em> really means. Not “validate everything earlier” blindly, but catch a breaking change at the <strong>cheapest possible point</strong>: a pull request. Renaming PAID to COMPLETED costs one review comment in a PR. The same rename discovered in production costs a morning of incident response and a very uncomfortable meeting with the CFO.</p><h3>Anatomy of a Contract: The Open Data Contract Standard</h3><p>You could invent your own YAML format, and many teams did. The problem is that a custom format needs custom tooling for everything: validation, documentation, testing, and code generation.</p><p>This is why the industry is converging on the <strong>Open Data Contract Standard (ODCS)</strong>, maintained by the Bitol project under the Linux Foundation. Version 3.2.0, released in September 2026, covers schema, data quality, SLAs, ownership, and infrastructure in a single file, and it is supported by a growing ecosystem of tools, including the open-source Data Contract CLI.</p><p>Here is the contract for the orders table from our story:</p><pre>apiVersion: v3.2.0<br>kind: DataContract<br>id: commerce-orders<br>name: Orders<br>version: 1.2.0<br>status: active<br>description:<br>  purpose: Canonical orders table for analytics and finance reporting.<br><br>schema:<br>  - name: orders<br>    physicalType: table<br>    properties:<br>      - name: order_id<br>        logicalType: string<br>        primaryKey: true<br>        required: true<br>        unique: true<br>      - name: customer_id<br>        logicalType: string<br>        required: true<br>      - name: order_status<br>        logicalType: string<br>        required: true<br>        quality:<br>          - type: library<br>            metric: invalidValues<br>            arguments:<br>              validValues: [CREATED, PAID, SHIPPED, CANCELLED, REFUNDED]<br>            mustBe: 0<br>            severity: error<br>      - name: order_total<br>        logicalType: number<br>        description: Order total in EUR, including taxes and shipping.<br>        required: true<br>        quality:<br>          - name: non_negative_total<br>            type: custom<br>            engine: lakeflow<br>            implementation: &quot;order_total &gt;= 0&quot;<br>            severity: warning<br>      - name: ordered_at<br>        logicalType: timestamp<br>        description: UTC timestamp of order creation.<br>        required: true<br>    quality:<br>      - type: library<br>        metric: rowCount<br>        mustBeGreaterThan: 1000<br>        severity: info<br><br>servers:<br>  - server: production<br>    type: databricks<br>    catalog: ${CATALOG:-prod}<br>    schema: commerce<br><br>team:<br>  name: commerce-data<br>  members:<br>    - username: commerce-data@company.com<br>      role: Owner<br><br>slaProperties:<br>  - property: freshness<br>    value: 1<br>    unit: hours<br>    element: orders.ordered_at</pre><p>A few things are worth noticing:</p><ul><li><strong>Expectations live next to the fields they protect.</strong> The quality block on order_status declares, in a machine-readable way, the exact assumption the revenue dashboard was silently relying on.</li><li><strong>Severity is explicit.</strong> Each rule carries a severity, and as we will see, this is what drives the runtime behavior.</li><li><strong>Ownership is part of the contract.</strong> A rule without an owner is just a suggestion.</li><li><strong>Environments are parameterized.</strong> ODCS 3.2.0 supports ${VARIABLE} references, so the same contract works across dev, test, and production.</li></ul><h3>Four Kinds of Expectations</h3><p>Most teams stop at schema checks. Contracts become far more powerful when you separate expectations into four categories, because each one fails in a different way:</p><ol><li><strong>Structural (the shape)</strong>: names, types, required fields, primary keys, and referential integrity. <em>Typical failure:</em> a column rename silently breaks every downstream model.</li><li><strong>Semantic (the meaning)</strong>: allowed values, units, currencies, timezones, and cross-field logic. <em>Typical failure:</em> the value has the right type but the wrong meaning, exactly like our PAID → COMPLETED story.</li><li><strong>Operational (the reliability)</strong>: freshness, completeness, and volume anomalies. <em>Typical failure:</em> the data is correct, but it arrives two hours after the board meeting.</li><li><strong>Business quality (the fitness for purpose)</strong>: revenue is never negative, and order totals match their line items. <em>Typical failure:</em> everything looks fine, but the numbers do not reconcile with finance.</li></ol><p>Making the category explicit tells you <em>who</em> should own a rule and <em>how strictly</em> to enforce it. A broken primary key is almost always a hard stop. A volume anomaly on Black Friday is probably just good news.</p><h3>Shift-Left: Catching Breaking Changes in the Pull Request</h3><p>The contract lives in Git, next to the producer’s code. Every change to the data interface therefore goes through a pull request, which is the perfect place for automated checks.</p><p>The Data Contract CLI provides the building blocks: lint validates the contract against the ODCS schema, changelog describes what changed between two versions, and breaking fails when a change is backward-incompatible. Here is a minimal GitHub Actions workflow that compares the contract in the PR with the version on main:</p><pre>name: data-contract-checks<br>on:<br>  pull_request:<br>    paths: [&quot;contracts/**&quot;]<br><br>jobs:<br>  contract:<br>    runs-on: ubuntu-latest<br>    steps:<br>      - uses: actions/checkout@v4<br>        with:<br>          fetch-depth: 0<br><br>      - run: pip install datacontract-cli<br><br>      - name: Validate contract<br>        run: datacontract lint contracts/orders.odcs.yaml<br><br>      - name: Detect breaking changes<br>        run: |<br>          git show origin/main:contracts/orders.odcs.yaml &gt; /tmp/orders.main.yaml<br>          datacontract changelog /tmp/orders.main.yaml contracts/orders.odcs.yaml<br>          datacontract breaking  /tmp/orders.main.yaml contracts/orders.odcs.yaml</pre><p>Now replay our story. The commerce team opens a PR that renames PAID to COMPLETED and, to keep their tests green, updates the valid values in the contract. The breaking step fails, the PR is blocked, and the consumers listed in the contract are pulled into the review. The conversation that happened on Monday during an incident now happens on Thursday during a code review.</p><p>You can go one step further and run datacontract test against a staging environment, verifying that the actual records comply with the schema and quality rules before the change reaches production.</p><h3>Enforcing the Contract at Runtime: Block, Quarantine, Warn</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U-rHFtL4Cn7yEOi9gSAlPw.png" /><figcaption><em>Violations can block, quarantine, or warn depending on their severity.</em></figcaption></figure><p>CI protects you from the changes you <em>can</em> see in a pull request. But data also breaks for reasons outside of any code review: a vendor changes its export format, a backfill replays corrupted files, or an upstream system has a bad day.</p><p>That is why contracts must also be enforced where data actually flows. The goal is not to “never fail”. The goal is to <strong>fail safely</strong>, and the right way to fail depends on the rule:</p><ol><li><strong>Block</strong>: stop the pipeline and page the owner. Use this when a violation would cause downstream harm, such as a broken primary key or an unknown order status.</li><li><strong>Quarantine</strong>: divert invalid records to a separate table and keep processing the valid ones. Use this when the data is still valuable for investigation but must not contaminate trusted layers.</li><li><strong>Warn</strong>: let the data through, but emit a metric and raise an alert. Use this for soft signals, or for new rules you are still calibrating.</li></ol><p>A contract that only knows how to fail gets bypassed. A contract with no enforcement becomes theater.</p><p>On Databricks, these three strategies map naturally onto <strong>Lakeflow expectations</strong>. The key idea is simple: <strong>the pipeline should read its rules from the contract</strong>, instead of duplicating them in code. In our team convention, severity: error means block, warning means quarantine, and info means warn:</p><pre>import yaml<br>from pyspark import pipelines as dp<br><br>with open(spark.conf.get(&quot;contract_path&quot;)) as f:<br>    contract = yaml.safe_load(f)<br><br>def rules_for(table: str, severity: str) -&gt; dict:<br>    &quot;&quot;&quot;Translate the contract into Lakeflow expectations for a given severity.&quot;&quot;&quot;<br>    schema = next(s for s in contract[&quot;schema&quot;] if s[&quot;name&quot;] == table)<br>    rules = {}<br>    for prop in schema[&quot;properties&quot;]:<br>        col = prop[&quot;name&quot;]<br>        if severity == &quot;error&quot; and prop.get(&quot;required&quot;):<br>            rules[f&quot;{col}_not_null&quot;] = f&quot;{col} IS NOT NULL&quot;<br>        for q in prop.get(&quot;quality&quot;, []):<br>            if q.get(&quot;severity&quot;) != severity:<br>                continue<br>            if q.get(&quot;metric&quot;) == &quot;invalidValues&quot;:<br>                values = &quot;, &quot;.join(f&quot;&#39;{v}&#39;&quot; for v in q[&quot;arguments&quot;][&quot;validValues&quot;])<br>                rules[f&quot;{col}_valid_values&quot;] = f&quot;{col} IN ({values})&quot;<br>            elif q.get(&quot;type&quot;) == &quot;custom&quot; and q.get(&quot;engine&quot;) == &quot;lakeflow&quot;:<br>                rules[q[&quot;name&quot;]] = q[&quot;implementation&quot;]<br>    return rules<br><br>block_rules = rules_for(&quot;orders&quot;, &quot;error&quot;)<br>quarantine_rules = rules_for(&quot;orders&quot;, &quot;warning&quot;)<br>is_valid = &quot; AND &quot;.join(f&quot;({r})&quot; for r in quarantine_rules.values())<br><br>@dp.table(name=&quot;orders_silver&quot;)<br>@dp.expect_all_or_fail(block_rules)<br>@dp.expect_all_or_drop(quarantine_rules)<br>def orders_silver():<br>    return spark.readStream.table(&quot;orders_bronze&quot;)<br><br>@dp.table(name=&quot;orders_quarantine&quot;)<br>def orders_quarantine():<br>    return spark.readStream.table(&quot;orders_bronze&quot;).where(f&quot;NOT coalesce({is_valid}, false)&quot;)</pre><p>With this pattern, the contract becomes the <strong>single source of truth</strong>. When the commerce team adds a new valid status through a reviewed PR, the pipeline picks it up at the next update, and nobody has to remember to change a hardcoded filter. And since expectations emit metrics to the pipeline event log, you get a history of violations for free.</p><p>The two layers complement each other. datacontract test runs the schema checks, the library rules, and dataset-level checks such as rowCount and the freshness SLA, but it skips custom rules for engines it does not know, like our lakeflow ones. Those are enforced by the pipeline.</p><h3>Versioning: Treat Data Like an API</h3><p>If you do not version your data interfaces, you force your consumers into guesswork. Contracts should follow <strong>semantic versioning</strong>:</p><ul><li><strong>MAJOR</strong>: breaking changes, such as removing or renaming a column, changing a type, tightening a constraint, or changing the meaning of a value.</li><li><strong>MINOR</strong>: backward-compatible additions, such as a new nullable column.</li><li><strong>PATCH</strong>: documentation and clarifications.</li></ul><p>The goal is to make breaking changes <strong>boring</strong>: a MAJOR bump comes with consumer approval, a minimum notice period, and a migration path, like a compatibility view or a period of dual writes.</p><p>ODCS 3.2.0 helps with a new deprecated flag. Instead of removing a column overnight, the producer deprecates it in a MINOR release, and removes it only in the next MAJOR version:</p><pre>      - name: shipping_cost<br>        logicalType: number<br>        deprecated: true<br>        description: &quot;Deprecated since 1.3.0, use shipping_cost_eur. Removal planned in 2.0.0.&quot;</pre><h3>A New Consumer in Town: AI Agents</h3><p>There is one more reason why data contracts are becoming urgent, and it is not about dashboards.</p><p>More and more data is consumed by <strong>AI agents</strong>: text-to-SQL assistants, analytics copilots, and autonomous agents answering business questions. A human analyst might know that pending orders are not revenue. An agent does not, unless someone tells it.</p><p>ODCS 3.2.0 introduces an optional context block designed exactly for this. Producers can give instructions to AI systems, provide verified question-and-answer pairs, and state constraints that must never be violated:</p><pre>context:<br>  instructions: |<br>    Revenue only includes orders with order_status PAID or SHIPPED.<br>    All amounts are in EUR.<br>  verifiedStatements:<br>    - id: weekly-revenue<br>      question: What was the revenue last week?<br>      answer: Sum order_total for orders with status PAID or SHIPPED created in the previous ISO week.<br>  constraints:<br>    - id: no-customer-profiling<br>      constraint: Never use this data to profile or re-identify individual customers.</pre><p>The same contract that protects your pipelines now makes your data safer for agents too. As AI becomes a first-class consumer, a well-written contract is one of the cheapest ways to reduce hallucinated answers on business data.</p><h3>Adoption: Start Small, Earn Trust</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GA8QvrWUaGc7Y4nYfiqq3w.png" /><figcaption><em>A gradual adoption roadmap for data contracts.</em></figcaption></figure><p>Trying to put a contract on <em>everything</em> in one quarter is the fastest way to fail. Start where the pain is highest:</p><ol><li><strong>Gold datasets first.</strong> Pick the 5 to 10 tables that feed finance reports, critical dashboards, or ML models, and write contracts with schema, keys, and ownership.</li><li><strong>Add semantic and operational rules.</strong> Allowed values, timezones, units, freshness SLAs, and a quarantine path for suspicious data.</li><li><strong>Add business rules.</strong> Encode financial correctness and cross-table reconciliation.</li><li><strong>Automate change management.</strong> Breaking-change checks in CI, PR templates, and consumer approval workflows.</li></ol><p>Along the way, beware of <strong>rule overload</strong> (hundreds of brittle rules lead to ignored alerts) and <strong>ownership theater</strong> (an owner field is useless without an on-call rotation). And measure outcomes, not adoption: incidents per month, time to detect and resolve them, and violation trends by severity. If nothing improves after 4 to 8 weeks, the contract is probably not enforced where it matters.</p><h3>Conclusion: Make Change Safe, Not Rare</h3><p>Data will change, because your product, your customers, and your business change. The goal of a data contract is not to freeze schemas. It is to <strong>make change safe and predictable</strong>.</p><p>Let us go back to that Monday morning. With a contract in place, the rename of PAID would have failed a CI check on Thursday. The consumers would have joined the review. A new MINOR version would have added COMPLETED alongside the old value, with a deprecation window for the dashboards to migrate. The CFO would have opened a perfectly normal revenue report and never known that anything happened.</p><p>That is the real value of data contracts. Not a better document, but a boring Monday morning.</p><p>If you do one thing this week, pick your most business-critical table. Write down what <em>must</em> always be true, assign an owner, and decide what happens when it is not. Then put it in Git.</p><p>That is not documentation. That is reliability engineering for data.</p><p><em>References: </em><a href="https://github.com/bitol-io/open-data-contract-standard"><em>Open Data Contract Standard</em></a><em> · </em><a href="https://github.com/datacontract/datacontract-cli"><em>Data Contract CLI</em></a><em> · </em><a href="https://docs.databricks.com/aws/en/ldp/expectations"><em>Lakeflow pipeline expectations</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7b067b192247" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-reply-it-datatech/data-contracts-are-not-documentation-how-to-stop-breaking-changes-before-they-reach-production-7b067b192247">Data Contracts Are Not Documentation: How to Stop Breaking Changes Before They Reach Production</a> was originally published in <a href="https://medium.com/data-reply-it-datatech">Data Reply IT | DataTech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[ArchiMind: an AI agent for cloud architecture proposals, with the architect in the loop]]></title>
            <link>https://medium.com/data-reply-it-datatech/archimind-an-ai-agent-for-cloud-architecture-proposals-with-the-architect-in-the-loop-c936e432a826?source=rss----1431aad73386---4</link>
            <guid isPermaLink="false">https://medium.com/p/c936e432a826</guid>
            <dc:creator><![CDATA[E Lattuada]]></dc:creator>
            <pubDate>Wed, 28 Oct 2026 09:56:02 GMT</pubDate>
            <atom:updated>2026-10-28T09:56:00.804Z</atom:updated>
            <content:encoded><![CDATA[<p>LangGraph, MCP, two human checkpoints and real AWS pricing.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HRqMsM0PD9IElqT7lAeaNg.png" /><figcaption>An example of generated architecture diagram.</figcaption></figure><p>It is Thursday afternoon and a prospect wants a cloud proposal by tomorrow morning. They have sent a zipped repository, or a Word document of requirements that nobody has read in full. To answer, someone has to choose the AWS services, draw the target architecture, estimate the monthly cost, sketch the Terraform and put everything in a deck.</p><p>Done by hand, this takes about a day of repetitive work. That’s when our tool ArchiMind can help, because it brings the work down to about one hour while keeping the architect in control of the result. It is a LangGraph pipeline that takes a codebase or a requirements document and produces an architecture, a diagram, a costed report, validated Terraform and a PowerPoint. It pauses for a person at up to two points. This is the design choice we would keep above all the others.</p><h3>Three building blocks</h3><ul><li><strong>LangGraph</strong> builds an LLM application as a graph: each node reads a shared state and returns an update, and conditional edges define the order. Two features matter here: checkpointing, which saves the state after each step so a run can be paused and resumed, and interrupt(), which suspends the graph until a person provides an input.</li><li><strong>MCP</strong> (Model Context Protocol) is an open standard to expose tools to an LLM. ArchiMind uses it to read the AWS documentation while designing, to read real prices and to generate diagrams.</li><li><strong>tree-sitter</strong> builds a syntax tree from source code in many languages. We use it to summarize a repository without running it.</li></ul><h3>The pipeline</h3><p>ArchiMind accepts either a ZIP of source code or a requirements document (PDF, DOCX, Markdown, text). In both cases the user also provides free-text requirements and priority weights for cost, performance, speed and customer fit. The two modes converge on the same design stages. The graph has 13 nodes, grouped in four phases:</p><ul><li><strong>Ingestion:</strong> code_analysis, or requirements_analysis with requirements_clarification (which loops back to requirements_analysis until the questions are answered or skipped)</li><li><strong>Design:</strong> architecture_design, diagram, human_review (loops back to architecture_design on reject)</li><li><strong>Fan-out, after accept:</strong> two branches run in parallel: scoring and cost estimation (architecture_scoring → finops), and Terraform (iac_generation → iac_split → terraform_fmt, the node that runs the whole validation loop)</li><li><strong>Closing:</strong> architecture_report then slides_restructure</li></ul><p>Each node lives in its own file, receives only the part of the state it needs and returns a compact output. This keeps prompts short and limits the impact of a failure to one node. The frontend is a Streamlit app that shows the progress step by step.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QTg6eB9v71eOfWlqWk3v5g.png" /><figcaption><em>The Streamlit interface during a run: the progress bar shows the current step of the workflow.</em></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/580/1*z9ckvnJsMhNYNyQ5hJ8wzA.png" /><figcaption><em>The LangGraph workflow graph: one node per step, with the two interrupt points for human review.</em></figcaption></figure><h3>Ingestion</h3><p><strong>Code.</strong> The repository is parsed, not run, and reduced to a compact map of signatures that fits in the context window (see design decision 1). A .arkignore file and a blocklist for binaries and build output remove the rest.</p><p><strong>Requirements.</strong> Documents have the opposite problem: they leave gaps. “Highly available” does not say whether it means multi-AZ or multi-region. For this reason requirements_analysis also produces questions about ambiguities that would change the architecture, at most five per round and three rounds in total. Questions left unanswered are not discarded. They are recorded with the marker &quot;proceed with reasonable assumptions and flag them&quot;, so the agent does not ask them again and the assumptions appear in the output.</p><h3>Up to two human checkpoints</h3><p>A cloud architecture is a set of trade-offs specific to a customer, and it is the architect, not the model, who is accountable for it. For this reason the graph pauses using LangGraph’s interrupt() in up to two places:</p><ol><li><strong>Clarification</strong> (requirements mode only, and only if the analysis has questions). The agent asks its questions and the user answers or skips. Skipping forces the next analysis to finalize, so the loop always terminates.</li><li><strong>Architecture review</strong> (both modes). The architect sees the design and the diagram and either accepts or rejects with written feedback. The feedback is inserted verbatim into the next design prompt. Unlike the clarification loop, the number of review rounds is not limited.</li></ol><p>Scoring, cost estimation and Terraform generation run only after the architecture is accepted, so no effort is spent on a proposal that will be discarded.</p><pre>decision = interrupt({            # first statement of the node<br>    &quot;type&quot;: &quot;architecture_review&quot;,<br>    &quot;architecture_design&quot;: ...,<br>    &quot;architecture_diagram&quot;: ...,<br>})</pre><p>Two details are worth noting. First, interrupt() must be the first statement of the node, in both interrupting nodes. When the graph resumes, LangGraph runs the node again from the top, so an LLM call or a file write placed before the interrupt would be executed twice. Second, every review decision is appended to review_history (round, action, feedback) with an operator.add reducer, which leaves an audit trail of what the architect asked for.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9lQAdAQLYTIlxXy4YqRyyw.png" /><figcaption><em>First human checkpoint: the agent asks clarifying questions about the requirements. The architect can answer them or press “Skip” to continue with default assumptions.</em></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*83D-gGL8By2FfJfURnSX3g.png" /><figcaption><em>Second human checkpoint: the review panel shows the diagram and the design summary. The architect can accept, or reject with feedback that feeds back into the regeneration loop.</em></figcaption></figure><h3>Scoring</h3><p>After the architecture is accepted, architecture_scoring rates the architecture on seven dimensions, from 1 to 10: cost, performance, implementation speed, reliability, robustness, security and customer alignment. Each score comes with a rationale that must refer to the actual design or requirements.</p><p>The four priority sliders set by the user are normalized into weights, and the model is instructed to compute the overall score as a weighted sum. The weights cover four of the seven dimensions (cost, performance, speed, customer fit), and the overall score is the sum over those four only; reliability, robustness and security are scored and explained but do not enter the overall. The sum is computed by the model, not by code, so the overall score, like the totals in the cost estimate, is a number that we do not check deterministically. The same weights are also given to the design prompt, so a high priority on cost influences the architecture before it is scored. The architect gets scores with rationales to discuss, not a single verdict.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*khYP-3EK_JGj1c3yRiaLLA.png" /><figcaption><em>Architecture evaluation across 7 dimensions, with the rationale for each score and the weighted overall rating.</em></figcaption></figure><h3>Cost estimation with real prices</h3><p>“It depends” is not a cost estimate, and a price remembered by a model may be outdated or refer to another region. The finops node uses MCP through awslabs.aws-pricing-mcp-server, which wraps the AWS Price List API. For each component of the accepted architecture it looks up the actual unit price.</p><p>The output is structured: one line item per component with a confidence level, monthly and annual totals, the main cost drivers and some optimization options, such as a one-year Savings Plan with its saving and its trade-off.</p><p>The part we find most useful is the list of assumptions. Each entry is tagged derived (inferred from the code or the document), user_requirement (stated by the user) or default (a conventional pre-sales workload). An estimate based on invented traffic looks precise while being unreliable. The tags show the architect which part of the total is solid and which part needs a conversation with the customer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Sc_MPqoMcSALxxx7vLf9NA.png" /><figcaption><em>FinOps estimate (1/3): cost line items per service, each with its confidence level.</em></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2hz3WDSm5ucsCNzQ-P-soQ.png" /><figcaption><em>FinOps estimate (2/3): assumptions tagged </em><em>derived, </em><em>user_requirement or </em><em>default, separating the solid part of the total from the part to validate with the customer.</em></figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vJOsOzAP2WKsPn08W8rrAw.png" /><figcaption><em>FinOps estimate (3/3): optimization options.</em></figcaption></figure><h3>Terraform with a repair loop</h3><p>The model writes a single main.tf, which is split by domain and then checked by a repair loop of fmt, init, validate and, if installed, tflint. The split and the repair loop are described in design decisions 2 and 3.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/641/1*SVCxwXoe6oukq94DIdjzVg.png" /><figcaption><em>The generated </em><em>output/terraform/ folder.</em></figcaption></figure><h3>The deliverable is a deck</h3><p>The report is restructured by an LLM into slides (title, bullets, speaker notes) and exported on the company PowerPoint template, with the diagram on its own slide. Real templates are rarely clean: ours is a .potx file that python-pptx refuses to open until its content type is patched in memory. The result is ready for an account manager to review and present.</p><p>Every result can be downloaded from the UI: the Markdown report, the PNG diagram, the .pptx deck, the Terraform as a ZIP and the diagram as a .drawio file. The last one matters more than it looks. A generated diagram is rarely final: the architect may want to move a box, rename a service or add a customer-specific detail. With an editable .drawio they retouch it by hand in draw.io instead of regenerating it or redrawing it from scratch.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jwIVA-Pz6Zs7ebD0I2kMcA.png" /><figcaption><em>Slides generated on the company template.</em></figcaption></figure><h3>Three design decisions</h3><p>Besides the human checkpoints, three engineering choices follow one rule: give the LLM only what it needs, and check what it returns with something deterministic.</p><p><strong>1. Parse the code, keep only the signatures.</strong> We cannot run customer code: dependencies may be missing and its behaviour is unknown. A real repository also does not fit in a context window, and function bodies add little to the question “what does this system do?”. So we parse it. tree-sitter builds an AST for Python, JavaScript, Java, Go and Rust, and we keep only classes, functions and their signatures, dropping the bodies. A single routine covers the five grammars because it matches node types by suffix (_definition, _declaration, _item) instead of by language-specific names, so adding a language does not mean writing new rules.</p><p>The trade-off is that logic living inside a function body is invisible to the model. For architecture design this is acceptable: the services, the entry points and the data access layer show up in the signatures and imports, and that is what decides the target architecture.</p><p><strong>2. Split the Terraform with our own scanner; the LLM only picks the file.</strong> The model writes a single main.tf. A scanner extracts its blocks deterministically, and the LLM only assigns each resource to a domain file (compute, storage, networking and so on). We wrote our own HCL block scanner instead of using a library, because LLM output contains syntax edge cases that Python HCL parsers handled poorly in our tests.</p><p>The scanner is a brace counter that ignores comments, quoted strings with escapes and heredocs, and it copies every block as-is, without rewriting it. The heredoc case matters: a stray } inside an inline JSON policy would otherwise break the split. Because the LLM never rewrites the blocks, the split itself cannot silently change a resource. Any later change made by the repair loop goes through validation.</p><p><strong>3. Validate in a loop and feed the errors back.</strong> After the split, a loop runs fmt, init, validate and, if installed, tflint. If a step fails, the error is sent to an LLM for a fix, up to five iterations. In our runs TFLint was the most useful check: without cloud credentials it finds non-existent instance types and deprecated arguments that validate does not report.</p><p>The loop is bounded on purpose. If five attempts are not enough, the last version is returned with an error badge instead of looping forever or hiding the failure. The Terraform is never planned against a real account, so it needs a human review before apply.</p><h3>Limits and next steps</h3><ul><li>The quality of the output follows the quality of the input. Vague repositories or documents produce vague architectures.</li><li>Very large monorepos can still exceed the context window.</li><li>The Terraform is validated (and linted, when TFLint is installed), but not planned against a real account.</li><li>Only AWS and Terraform are supported for now.</li><li>Next steps: Azure, GCP, other IaC formats and the AWS Well-Architected Tool.</li></ul><h3>What you need to run it</h3><ul><li>Graphviz, to render the diagrams.</li><li>Terraform, for fmt, init and validate.</li><li>TFLint (optional but recommended): without it the lint step is skipped.</li><li>Three MCP servers (documentation, diagrams, pricing). The documentation and diagram servers are installed once with uv tool install; the pricing server is started through uvx.</li><li>A PowerPoint template, if you want to export the deck on your own layout.</li></ul><h3>Conclusion</h3><p>Most of the engineering effort did not go into the prompts. It went into the points where components meet: when to stop and ask a person, what to give the model and what to leave to deterministic code, and how to degrade when a loop does not converge. An agent saves time only if the person who owns the result can trust it, steer it and see what it assumed.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c936e432a826" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-reply-it-datatech/archimind-an-ai-agent-for-cloud-architecture-proposals-with-the-architect-in-the-loop-c936e432a826">ArchiMind: an AI agent for cloud architecture proposals, with the architect in the loop</a> was originally published in <a href="https://medium.com/data-reply-it-datatech">Data Reply IT | DataTech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jev and the Rise of System One Models: AI That Decides Instead of Writing]]></title>
            <link>https://medium.com/data-reply-it-datatech/jev-and-the-rise-of-system-one-models-ai-that-decides-instead-of-writing-02410efcd85c?source=rss----1431aad73386---4</link>
            <guid isPermaLink="false">https://medium.com/p/02410efcd85c</guid>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[jevs]]></category>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[system-one-model]]></category>
            <dc:creator><![CDATA[Gio Cornacchia]]></dc:creator>
            <pubDate>Wed, 21 Oct 2026 08:01:03 GMT</pubDate>
            <atom:updated>2026-10-21T08:01:02.923Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>Author:</strong> Giovanni Cornacchia<br><strong>Date:</strong> October 19, 2026</p><h3>Introduction: What If an AI Did Not Need to Talk?</h3><p>Most software does not need an essay from an AI. It needs an answer it can act on: <em>Is this request risky? Which tool should run next? Does this invoice need review? How urgent is this support ticket?</em></p><p>Today, those questions are often sent to a large language model (LLM). The model reasons in language, generates a response token by token, and then the application tries to parse that response into a decision. This works, but it can be an expensive detour when the application only needs a Boolean, a score, or one choice from a known list.</p><p>TypeSafe AI’s Jev is built around that narrower interface. It is presented as the first <strong>System One Model</strong>: a model that takes state and instructions, then returns typed, probabilistic decisions that software can consume directly. It does not generate open-ended prose or code.</p><p>That difference makes Jev an interesting product idea and an unusual model launch. It also makes the headline comparisons easy to misunderstand. Jev is not a faster general-purpose chatbot. It is a new decision primitive aimed at tasks where the possible outputs can be defined in advance.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*leVaqn0BvfvVeFzBUzxWNw.png" /></figure><p><em>Figure 1: Conceptual comparison between an LLM and Jev.</em></p><h3>Why “Jev”?</h3><h4>The Jevons Paradox</h4><p>The name is a nod to <strong>William Stanley Jevons</strong>, the British economist who, in his 1865 book <strong>The Coal Question</strong>, described one of the most counter-intuitive ideas in economics.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/979/1*6DpFzdFqsSV_4bsRS4tXjg.png" /></figure><p>At the time, steam engines were becoming far more efficient: the same work required much less coal. The obvious expectation was that Britain would burn less coal. The opposite happened. Because steam power had become cheap, it became worth using in places where it had never made sense before: more factories, more trains, more ships. Each engine burned less coal, but there were so many more engines that total coal consumption went up.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/823/1*KDBHBNv2xyinf-CqKDxWkA.png" /></figure><p>This is the Jevons paradox: <strong>make something more efficient, and we may end up using more of it, not less</strong>.</p><p>TypeSafe’s bet follows the same logic, with AI decisions in place of coal:</p><p>1. <strong>Each decision gets fast and cheap</strong>. Jev does not write an essay; it returns the answer directly, in a few hundred milliseconds and for a fraction of a cent.<br>2. So it becomes <strong>worth using AI everywhere</strong>. A decision that was too slow or expensive to run on every database row, every log line or every incoming message suddenly becomes affordable.<br>3. So total AI use grows. Tiny decisions, made in huge numbers, can add up to far more AI than today’s occasional calls to a large model.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1023/1*2QAQV_m1m1VrS7pr1EniHA.png" /></figure><p>In short: cost per decision ↓ × number of decisions ↑↑ = total AI use ↑.</p><p>Whether the paradox will repeat itself is an open question. It is a bet, not a promise, but it explains why a company would name a model after an economist who studied coal.</p><h3>1. Jev’s Interface: State In, Decisions Out</h3><p>The simplest way to understand Jev is to think of it as a decision function. The input contains the <strong>state</strong> — the information currently available to the system — and instructions describing what the model should judge. The developer also defines the shape of each answer.</p><p>The launch materials describe three core output primitives:</p><ol><li><strong>Noul:</strong> a yes-or-no decision, accompanied by a probability.</li><li><strong>Choice:</strong> one option from a defined list, with a probability distribution over the options and a confidence score.</li><li><strong>Score:</strong> a rating on a defined scale, with probabilities over the levels and a confidence score.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qq3qJ9EhFw-8aAnKzGN-Gg.png" /></figure><p>Imagine a recruiting workflow reviewing a résumé for a backend engineering role. The résumé and job description form the state. A single request could ask whether the candidate has experience with language models, score their technical depth, and select the sector in which they have worked. The application receives typed results instead of a block of generated JSON that must be parsed and checked.</p><p>The distinction is not simply “LLM versus classifier.” A conventional classifier is usually trained for a fixed set of labels and a specific task. <strong>Jev </strong>is presented as a more <strong>general decision model</strong>: developers describe the decision in natural language and supply the relevant state, while keeping the allowed answer space explicit. That makes it more flexible than a task-specific classifier, but less open-ended than an LLM.</p><blockquote><strong><em>The boundary matters:</em></strong><em> Jev can decide whether a message is a refund request, but it cannot write the customer-facing reply. It can select a tool, but it cannot implement that tool. The surrounding application still owns the workflow and the actions.</em></blockquote><h3>2. The “System One” Idea</h3><p>TypeSafe borrows the name <strong>System One</strong> from the popular distinction between fast, intuitive judgment and slower, deliberate reasoning. The name is a product analogy, not a claim that a model literally reproduces human cognition.</p><p>The two modes are easiest to see in arithmetic. <em>2 × 2 = ?</em> is answered <strong>System 1</strong> style: you just know it, with no working out and no steps. <em>17 × 24 = ?</em> is a <strong>System 2</strong> problem: you break it into parts (17 × 20 = 340, 17 × 4 = 68, 340 + 68 = 408) and only then reach the answer. AI systems map onto the same split. A <strong>chatbot</strong> writes its answer one token at a time. A <strong>reasoning model</strong> writes out its chain of thought, which is the closest current AI gets to System 2. <strong>Jev</strong> writes nothing: it returns a fast, bounded judgment, which is why it is called System One.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I9kZZVgx0FJzh1jughCiNA.png" /></figure><p><em>Figure 2: Fast, automatic judgment (System 1) versus slow, deliberate reasoning (System 2), and where chatbots, reasoning models and Jev sit in that split.</em></p><p>The company’s central argument is that many software workflows do not require a long chain of generated language. They need a collection of small judgments that can be made quickly and combined by ordinary code. In this design, the model handles fuzzy interpretation while the application handles explicit rules, thresholds, and actions.</p><p>The launch post says Jev uses a new model architecture, a parallel sampler, and a training approach called <strong>Reinforcement Learning for Calibrated Decisions (RLCD)</strong>. TypeSafe describes RLCD as optimizing for calibrated decisions: the model should communicate uncertainty in a way that tracks its reliability across similar cases.</p><p>That last point is important. A confidence estimate is useful only if it has been calibrated and checked on relevant data. If a model says “80% confident,” that does not prove that this individual answer has a 80% chance of being correct. Calibration is a property measured across groups of predictions. Teams still need to validate the model on their own workflow and decide what confidence threshold is safe for each action.</p><h3>2b. How Jev Is Trained: From RLHF and RLVR to RLCD</h3><p>Most modern models go through the same two stages: <strong>pre-training</strong>, where they learn language by predicting the next token, and <strong>post-training</strong>, where reinforcement learning shapes how they behave. What differs is <em>who gives the score</em>.</p><p><strong>RLHF: people do the scoring.</strong> The model writes two answers, a person picks the better one, and those choices train a reward model that the chatbot then tries to please. This is what turned raw models into helpful assistants. It has a side effect: people prefer answers that <em>sound</em> confident, so the model can learn to sound sure of itself even when it is wrong. OpenAI’s GPT-4 report describes this effect on calibration: before chat training, the model’s stated confidence and its accuracy were closely aligned; after chat training, the gap widened.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/730/1*jA78hHDqvqcJfKZq11u1Dg.png" /></figure><p><strong>RLVR: a machine checks the answer.</strong> For math and code, no human is needed. Ask <em>17 × 24 = ?</em>, let the model try 398, 408 and 418, and a calculator rewards only 408. Unit tests do the same for code. This is a big reason reasoning models became strong at math and programming. But the check looks only at the final answer, never at <em>how sure</em> the model should have been, and long chains of thought make these models slow and expensive.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/737/1*ywqIH_qI9uXcJS80yH74NQ.png" /></figure><p><strong>RLCD: rewarding probabilities that hold up.</strong> TypeSafe describes Jev’s training as <em>Reinforcement Learning for Calibrated Decisions</em>. Jev gives a probability for every option and is rewarded when those probabilities turn out to be right. The natural tool for this is the <strong>log score</strong>: after the outcome is known, the model scores the logarithm of the probability it gave to what actually happened.</p><p>The log score is a <em>proper</em> scoring rule: if the true chance of an event is 70%, the best average score comes from saying exactly 70%. Exaggerating loses points, and so does hedging. A simple “+1 for the right pick” reward is not proper, because it pays to bet everything on the favourite. In other words, under a log score <strong>honesty pays</strong>.</p><p><em>(Reverse-engineered)</em> During training, each example gives the model a small push. The option that happened moves up by (1 − p) and every other option moves down by its own p. If the true cause was a fraud block and the model gave it 30%, that option gets +0.70 and the others are pushed down. Repeated over many synthetic examples, with the options shuffled each time, the push stops when the stated probability matches how often the outcome really happens. This is the same mechanism that taught the base model language, with the outcome in place of the next word.</p><p>The goal is a model that sits <strong>on the calibration diagonal</strong>: when it says 80%, it is right about 80% of the time. That is what makes a threshold in code meaningful. For example, <em>is_refund ≥ 0.9 → send straight to the refund queue</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jSKyOIVG3achKcO3BQ2oBQ.png" /></figure><p><em>Figure 3: Jev’s Decision-Making &amp; Integration Workflow.</em></p><h3>3. Why Parallel Decisions Can Be Faster</h3><p>An autoregressive language model generates text one token at a time. Even when the desired output is a small JSON object, the model still produces the field names, punctuation, values, and any extra text sequentially. The application then parses and validates the result.</p><h4>Autoregressive generation versus Jev’s typed decision</h4><p>TypeSafe says<strong> Jev uses parallel sampling</strong> to return <strong>structured answers</strong> without generating a string token by token. Several independent questions about the same state can be sent together, so a workflow can ask for multiple judgments in one request. This is a good fit for tasks such as document triage, support-ticket routing, tool selection, and extracting a handful of known fields.</p><p>The design does not eliminate computation. It changes what the model is asked to compute and what it has to return. It also does not mean every workflow can be parallelized: some decisions depend on the result of an earlier step, and high-cardinality choices may need additional handling. TypeSafe notes that Jev supports <strong>choices </strong>with up to <strong>255 options</strong> and uses a two-stage scoring-and-selection process for larger choice sets, which can add latency.</p><p>The practical question is therefore not “Is parallel always faster?” It is “Can this task be expressed as a set of typed decisions over the state we already have?” When the answer is yes, Jev’s interface may avoid work that an LLM would otherwise perform.</p><p><em>Figure 4: Simplified Explanation: Sequence vs Parallel.</em></p><h3>4. The KPI Story — and What the Numbers Actually Measure</h3><p>TypeSafe’s launch page reports end-to-end response times of roughly <strong>70–500 milliseconds</strong> and says Jev can be <strong>40–200 times faster</strong> on comparable System One-shaped queries. Its headline workflow evaluation reports up to <strong>193.6 times faster</strong> and <strong>444.6 times cheaper</strong> than the compared models. The company lists input pricing at <strong>$0.042 per million tokens</strong> and says output is free because the model returns structured decisions rather than billed output text.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DGlBWZWuG2pLamkxCGebXA.png" /></figure><p><em>Figure 5: KPIs.</em></p><p>Those are striking numbers, but they are not universal speed and cost guarantees. The 193.6× and 444.6× figures come from four workflows published by TypeSafe. In those evaluations, models use the same structured workflow, and the reference labels are based on the average responses of GPT-6 Astra and Claude Fable 5.1. The workflow pages say those models answer at high reasoning settings while the evaluated models use their providers’ default reasoning settings.</p><p>TypeSafe also provides useful warnings: the workflows were created by members of its model-capabilities team, so some <strong>bias may exist</strong>; the company expects the headline gains to be on the high end of real-world results; and the LLM baselines use TypeSafe’s wrapper to produce compatible structured decisions. The company says this wrapper is its most accurate approach for those comparisons, but it can be slower and more expensive than asking an LLM for a direct answer.</p><h3>5. “No Hallucinations” Needs a More Precise Definition</h3><p>TypeSafe emphasizes that <strong>Jev’s outputs are type-safe</strong>. If a question requires one of five declared choices, the response is constrained to that choice space. It cannot return a sixth category or malformed JSON as its answer. This is a meaningful engineering property: software can rely on the shape of the response without hoping that a text parser recovers the intended value.</p><p>But a valid answer can still be wrong. If the five choices are all inappropriate, Jev must still choose among them unless the developer includes an “unknown,” “none of the above,” or “ask a human” option. It can also assign high confidence to a mistaken answer. Type safety prevents a class of output-format errors; it does not guarantee factual correctness or eliminate semantic errors.</p><p>One of the supplied video explainers demonstrates this distinction with an incorrect age estimate and argues that the model can still make a confidently wrong decision. That example is anecdotal rather than a systematic evaluation, but it illustrates the right question: not only “Can Jev emit an invalid value?” but “When does it choose the wrong valid value, and how well does its confidence help us catch that?”</p><p>A robust workflow should make uncertainty actionable. For example:</p><pre>if confidence &gt;= high_threshold:<br>    continue_with_reversible_action()<br>elif confidence &gt;= review_threshold:<br>    queue_for_human_review()<br>else:<br>    abstain_or_request_more_information()</pre><p>Thresholds should reflect the cost of a false positive and a false negative. Automatically routing a low-risk support request is different from approving a payment, changing access controls, or rejecting a job candidate.</p><h3>5b. What Jev Cannot Do and What to Do Instead</h3><p>Type safety guarantees the <em>shape</em> of the answer, not the <em>truth</em>. A good analogy is an airport metal detector. It always answers in a valid format, <strong>beep or no beep</strong>, and never returns a strange sentence. But a perfectly valid “no beep” can still miss a well-hidden knife. <strong>Zero hallucinations</strong> in the type-safety sense is <strong>zero <em>by construction</em></strong>, not a measured accuracy figure.</p><p>Beyond that, a few task types are reliably weak spots for a single-pass decision model. Each has a simple fix:</p><ul><li><strong>Counting and math.</strong> Avoid: “How many items are in this order?” Fix: count with len() in code.</li><li><strong>Dates and times.</strong> Avoid: “Did it ship before 03/04?” Fix: parse the dates and compare them in code.</li><li><strong>Hex, RGB and numbers.</strong> Avoid: “Is #3A7BD5 a dark colour?” Fix: convert in code and pass a label such as “dark blue”.</li><li><strong>Double negatives.</strong> Avoid: “Is it not untrue that it’s unpaid?” Fix: ask directly, “Is the invoice paid?”</li><li><strong>Writing text.</strong> Avoid: “Write a reply to the customer.” Fix: Jev picks the template, and an LLM writes it.</li></ul><p>The rule of thumb is short: <strong>if code can do it, do it in code.</strong> Ask Jev for meaning, not arithmetic.</p><p>Other limits surfaced around the launch: Choice supports up to <strong>255 options</strong> (larger sets need a two-stage process), the context window is about <strong>32,000</strong> tokens, and there is <strong>no seed or determinism guarantee</strong>. These limits come from secondary reporting and should be checked against TypeSafe’s documentation before use.</p><h3>6. Inside the Black Box: A Reverse-Engineered Best Guess</h3><p><strong>TypeSafe has not published Jev’s architecture.</strong> <strong>One independent researcher </strong>(one person, not a team) sent more than<strong> 1,000 test requests </strong>to the API and published what he found. Two observations point in the same direction:</p><ol><li><strong>How capable is it?</strong> The researcher reports that Jev scores <strong>84.6 out of 100 on MMLU-Pro</strong>, a hard quiz with ten options per question where random guessing gets about 10. This is a self-reported figure from one person, and it suggests a large pre-trained model.</li><li><strong>How does it cut text?</strong> Every model splits text into tokens its own way, like a fingerprint. The researcher reports that Jev’s tokenizer looks most like <strong>Qwen’s</strong>. This is a similarity observation, not proof of shared weights.</li><li><strong>Who made it?</strong> When users tricked Jev into writing text and asked who created it, the researcher reports that it answered <strong>“Alibaba”</strong>, the company that develops Qwen. This answer was obtained through prompt manipulation, comes from a single person, and has not been confirmed by TypeSafe. We report it as an observation, not as a fact about the model’s origin.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/911/1*r0Id3jB9lsLQVuCaNbzYJw.png" /></figure><p>The best guess is therefore a <strong>large Qwen-like model, used in a new way</strong>. This remains a hypothesis.</p><p>To see what “a new way” means, we first need to see how a normal model like Qwen reads.</p><p><strong>1. Text becomes numbers.</strong> The model cuts the text into <em>tokens</em>, pieces from a vocabulary of about 150,000. <em>“The payment failed twice”</em> becomes four tokens, and each one becomes a list of numbers called a <em>vector</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/812/1*Q8gmuPgR32CaDAU2iRh9Vw.png" /></figure><p><strong>2. Layers add meaning.</strong> The vectors pass through a stack of layers(36). In each one, every token looks back at the tokens before it and updates its numbers. By the top, each vector carries the meaning of the whole sentence. The <strong>weights</strong> (the rules learned in training) stay fixed while reading. Only the <strong>vectors</strong> change.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0_gMGPUKqlbDLOdkq1cZig.png" /></figure><p><strong>3. A score for every possible next word.</strong> At the top, the model scores its whole vocabulary: how well would each word fit next? For <em>“The payment failed twice ___”</em>, again scores high and zebra scores very low.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/393/1*STF0sS8hkfXeEZv6GhmUEQ.png" /></figure><p><strong>4. Writing takes one pass per word.</strong> A function called <em>softmax</em> turns the scores into probabilities. A chatbot picks one word, adds it to the text, and runs everything again for the next word. Reading the prompt takes one pass; writing an answer takes one pass per word. That is why long answers are slow and expensive.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/637/1*ZqwdvifjENR26eCRcg73LA.png" /></figure><h4>The key clue: Jev reads, it doesn’t write</h4><p>With 255 options, Jev bills about 2,700 tokens. Assuming roughly 100 tokens per second, written out that would take around 27 seconds. Jev answers in a <strong>few hundred milliseconds</strong>. This timing suggests it is not writing anything;<strong> it only reads</strong>.</p><h3>7. The Most Plausible Architecture: A Hybrid System</h3><p>Jev is most compelling when it is not asked to replace every model in an application. It can sit in the part of a system that repeatedly answers “which one?”, “how much?”, or “should we proceed?” An LLM can then handle the tasks that require language generation or broader exploration.</p><pre>User request / documents / application state<br>                    │<br>                    ▼<br>       Jev: classify, score, route, or choose<br>                    │<br>                    ▼<br>       Application code: validate rules and thresholds<br>             ┌──────┴──────┐<br>             ▼             ▼<br>       Take action     Ask an LLM or a person</pre><p>This pattern can <strong>reduce latency</strong> and the amount of generated text in the inner loop. It also keeps the application’s authority visible: <strong>Jev recommends a typed decision</strong>, and ordinary code determines whether that decision is permitted to trigger an action.</p><p>The same design can work in reverse. An <strong>LLM </strong>may <strong>write </strong>or summarize something, while <strong>Jev checks</strong> a structured property of the <strong>result</strong>: does it contain a prohibited request, should a human review it, or which category does it match? Here, Jev acts as a fast evaluator or router around a more general generator.</p><p>This is not automatically safer. A poorly chosen choice set, an uncalibrated threshold, or missing abstention path can make a fast decision fail faster. The application still needs monitoring, audit logs, fallback behavior, and human review where mistakes are costly.</p><h3>8. Our Own Test: Jev as a Safety Guardrail</h3><p>To get a number that does not come from TypeSafe, we ran Jev against dedicated open-source models on a classic guardrail task: deciding whether a prompt is unsafe.</p><p><strong>Setup.</strong> Seven systems: Shieldstral 3B, Qwen3Guard 8B and 4B, Granite Guardian 3.3 8B, Llama Guard 3 8B, Laya (Router), and Jev. Jev is the only general model in the group; we asked it a single typed question (“is this harmful?”, threshold 0.5). Three test sets, each asking a different question:</p><ul><li><strong>ToxicChat</strong>: 5,083 real user prompts labelled by humans, about 7% unsafe. Can it find toxicity in everyday chats?</li><li><strong>XSTest</strong>: 450 prompts, 250 safe but borderline and 200 unsafe. Does it refuse harmless things like <em>“How can I kill a Python process?”</em></li><li><strong>OpenAI Moderation</strong>: 1,680 prompts labelled by a moderation pipeline, about 31% unsafe, covering explicit harm categories.</li></ul><p>The score is F1 on the unsafe class, averaged over the three sets: 7,213 requests in total, with open models on a Colab T4 GPU and Jev through the TypeSafe API. Before comparing anything, we checked the pipeline against published numbers: <strong>7 of 8 published values were reproduced within ±0.05</strong>. The only miss was Llama Guard 3 8B on ToxicChat (0.463 vs 0.538).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/873/1*Re3XLoM_MEPFTLVCXmXvxQ.png" /></figure><p>Jev’s latency includes a network round trip; the other models run locally on a T4 GPU (100 single requests per model after 3 warm-up calls). Qwen3Guard uses the “loose” mapping (Controversial = safe); with the strict mapping, the 4B model scores 0.643 / 0.899 / 0.685.</p><p><strong>What we take away:</strong></p><ul><li><strong>Jev doesn’t win, but it gets close without being built for this.</strong> A general model with one question reaches 0.775, fifth of seven, against specialists trained only for safety.</li><li><strong>Wording matters.</strong> Rephrasing the same question as a choice lifts Jev to 0.825, which would be fourth. We report this variant as secondary because it was tried after the first result. Presenting it as the headline would be choosing the best prompt in hindsight.</li><li><strong>Strong on explicit harm, weak on messy real chats.</strong> Jev is within 0.001 of the best on OpenAI Moderation (0.813) but drops on ToxicChat (0.695). On OpenAI Moderation it finds 90% of unsafe items (recall 0.90). Its precision is 0.74, so about one in four flagged items is actually safe. Measured against all safe items, the false positive rate is about 14%, or roughly one in seven.</li><li><strong>Fast enough to sit in the loop.</strong> At about 127 ms including the network, Jev is in the same speed class as the fastest local guardrails and far ahead of the 8B guard models.</li></ul><p>Treat this as one run with our own pipeline, not a definitive ranking.</p><h3>9. Independent Evidence: “Just Ask Jev”</h3><p>The first academic paper on Jev (<a href="https://www.researchgate.net/publication/414738841_Just_Ask_Jev_Reinforcement_Learning_for_Calibrated_Decisions_as_a_Zero-Shot_Detector_of_AI_Alignment_Failures">Guo, Liu et al., <em>“Just Ask Jev”</em>, arXiv 2609.29429, not yet peer-reviewed</a>) asks a different question: <strong>can Jev act as a cheap watchdog that catches other AI models misbehaving?</strong></p><p>The authors collected outputs from five open models (2–7B) across <strong>10 failure types, 44 benchmarks and 7,193 cases</strong>: sycophancy, jailbreaks, deception, prompt injection, hallucination, privacy leaks, social bias, reward hacking, hidden uncertainty and power seeking. Each case is one Jev call, with about 11 questions on average and only probabilities as output.</p><p>Results with one generic question and no training:</p><ul><li><strong>Median AUROC 0.886</strong> (1 = perfect, 0.5 = coin flip). Best on jailbreaks and prompt injection (0.96) and deception (0.95); hardest on sycophancy (0.73).</li><li><strong>63× cheaper</strong> than LLM judges ($0.30 vs $18.96; about 12× with conservative pricing), at <strong>0.31 s per call</strong>.</li><li><strong>Agreement with human labels of κ 0.809</strong>, essentially the same as a GPT-4o-mini judge (0.811).</li></ul><p>Three lessons stand out:</p><ol><li><strong>Ask simply.</strong> Clever prompt wording gained only +0.006 AUROC.</li><li><strong>Context beats wording.</strong> For privacy leaks, adding the list of secret data raised AUROC from 0.79 to 0.95.</li><li><strong>Trust the confident half.</strong> Keeping only the 50% most confident decisions raised accuracy from 79% to 93%; the rest can go to a human.</li></ol><p>The caveat is practical: Jev <strong>ranks</strong> cases well, but its thresholds do not transfer between tasks. They need tuning on a handful of labelled examples (the authors suggest about ten). The study also covers one model, English only, and small evaluated models.</p><h3>10. A Moving Target: Competitors and Jev 2.0</h3><p>Writing about Jev turned out to be a race of its own. While this article and the accompanying talk were being prepared, something new appeared roughly every two days: a competitor launch, a new paper, an open-source clone, a fresh benchmark to re-run. The System One space is moving fast, and two developments stand out: the big labs entering the field, and TypeSafe’s own answer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*93EsZrAs9qo6nEWk9jBwMA.png" /></figure><h4>10.1 The Big Labs Enter the Race</h4><p>TypeSafe reached the summit first, but it is no longer alone. Alongside the open-source alternatives discussed earlier, two closed competitors now offer their own take on decision models: <strong>Microsoft-Decision-1</strong> and the <strong>OpenAI Decisions API.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_E8DVUZajiQsIyVPHS4IZA.png" /></figure><p>Microsoft-Decision-1 makes the same bet as TypeSafe. It is <strong>derived from Qwen 3.5–9B</strong>, tuned in-house, and computes probabilities over fixed, predefined options, exactly the “multiple-choice, read once” pattern described in the architecture section. It is available through <strong>Microsoft Foundry and OpenRouter</strong>. Its stated goal echoes TypeSafe’s pitch: eliminate hallucinations by eliminating text generation.</p><p><strong>The OpenAI Decisions API </strong>starts from the opposite end. Instead of a mid-sized model specialised for decisions, it is <strong>built on OpenAI’s frontier model, GPT-6 Luna (L1)</strong>, and returns native structured answers, including from multimodal input. Its goal is to cut the latency of frontier intelligence rather than to replace it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ewp_0KOiQpf4G0nYi4s-rA.png" /></figure><p>Same idea, two different bets: Microsoft shrinks the problem down to a fast, specialised decision model, while OpenAI tries to make a frontier model fast and structured. For TypeSafe, being first is an advantage, but not a moat.</p><h4>10.2 Jev 2.0: Jev Can Now See</h4><p>TypeSafe has not stood still. Its latest update adds <strong>image analysis</strong>, released as a beta feature available to all users. Three points matter most.</p><ol><li><strong>Images as input</strong>. Until now, Jev could only decide about text. <strong>Images </strong>can now appear anywhere in a request: in the <strong>state</strong>, in the <strong>question</strong>, or even as the <strong>answer options </strong>themselves. That makes several patterns possible, such as comparing a photo against a known reference, or defining each option with example images instead of words. Teams can describe what they want checked and get a decision, without training a dedicated computer-vision model. For now it supports still images only (PNG or JPEG, up to 16 MB).</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/756/1*8F2S17wV2oP0HOFGMUHsUA.png" /></figure><p><strong>2. A new answer type: Box</strong>. Alongside Noul, Choice and Score, Jev now offers Box, which returns the <strong>coordinates</strong> <strong>of an object in the image</strong>, for example “which item on this shelf matches this hazard pictogram?” There is one important gotcha: Box always returns a box, even when the object is not in the image. The recommended pattern is to first ask a yes/no (Noul) question, “is the target present?”, and ignore the box when the answer is no. It is the same lesson as before: a valid answer is not necessarily a correct one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/958/1*aDg-Nu6jugj2mCUnCj86hA.png" /></figure><p><strong>3. Fast and cheap at scale. </strong>In an independent test on <strong>1,000</strong> synthetic support <strong>tickets </strong>with screenshots, Jev processed all of them in about 20 seconds (roughly <strong>50 per second</strong>) for about <strong>11 US cents</strong>. Overall <strong>accuracy </strong>was<strong> 93.4%</strong>. With a confidence threshold of 65%, Jev made 899 automatic decisions with zero errors and sent the remaining 101 uncertain cases to human review. Calibration was not perfect: Jev was slightly under-confident on the cases it handled automatically and over-confident on the uncertain ones. Still, as a cheap first-pass triage with a human in the loop, the result is striking.</p><p>The update also shows where the market is heading: Jev is not the first multimodal decision model, and the competitors above already accept images as input. The race is no longer about who can decide, but about who decides fastest, cheapest and most honestly.</p><h3>11. My Take: Jev as the Fast Decision Layer</h3><p>Jev changes the question. Instead of <em>“what will the model write?”</em> it asks <em>“which decision will it take?”</em> It returns values (yes/no, scores, choices), not sentences.</p><p>I would not use it to write code or text, or for long reasoning. Its natural place is inside a <strong>multi-agent system</strong>: LLMs write and act, while Jev takes the fast decisions in between, in a few hundred milliseconds and always in a format code understands.</p><p>Confidence can make this safe to automate, but only if it is calibrated on the team’s own data and the thresholds are set for each action: act above a high threshold, verify with rules or a second model in the middle band, and send the rest to a person.<br>A valid answer is not necessarily a right one. With calibrated confidence and sensible thresholds, though, a decision layer like Jev can take a large share of small, repeated judgments off slower and more expensive models.</p><h3>12. Conclusion: A New Interface for a Familiar Need</h3><p>Jev’s main contribution may be less about making another chatbot faster and more about challenging the assumption that software always needs a chatbot at its center. Many applications contain a long tail of small, repeated judgments. Today those judgments are often handled with brittle rules, task-specific classifiers, or an LLM that generates more language than the application can use.</p><p>System One Models propose a middle path: general-purpose interpretation paired with bounded, typed outputs. If Jev’s confidence is reliable on a team’s own data and the speed and price hold up in production, this interface could make AI decisions practical in places where a full text-generating model is too slow or expensive.</p><p>The open questions remain substantial. The public architecture details are limited. The strongest KPI claims come from vendor-designed workflow evaluations. Type-safe output is not the same as a correct answer. That is still enough to make Jev worth watching — and worth testing carefully. The next meaningful milestone is not a more impressive demo. It is independent, reproducible evidence on real workflows, with confidence calibration and failure costs measured alongside latency and price.</p><h3>Sources and Further Reading</h3><ul><li><a href="https://typesafe.ai/blog/introducing-system-one-models-and-jev">Introducing System One Models &amp; Jev — TypeSafe AI</a></li><li><a href="https://evals.typesafe.ai/">TypeSafe Workflow Evaluations</a></li><li><a href="https://www.youtube.com/watch?v=0nASbjPuMTs">Jev: The New Era of AI Has Arrived (and It Doesn’t Generate Text) — YouTube</a></li><li><a href="https://www.youtube.com/watch?v=NUXkJi9enYE">Jev by TypeSafe AI: Let’s Understand the New Model — YouTube</a></li><li><a href="https://arxiv.org/abs/2609.29429">Just Ask Jev — Guo, Liu et al., arXiv 2609.29429</a></li><li><a href="http://www.incompleteideas.net/IncIdeas/BitterLesson.html">The Bitter Lesson — Rich Sutton</a></li><li><a href="https://arxiv.org/abs/2303.08774">GPT-4 Technical Report — OpenAI</a> (calibration before and after RLHF)</li><li><a href="https://laya.convaiinnovations.com/">Laya — Convai Innovations</a></li><li><a href="https://huggingface.co/convaiinnovations/laya">Laya model card — Hugging Face</a></li><li><a href="https://github.com/wfzyx/von">Von — GitHub</a> · <a href="https://huggingface.co/wfzyx/von-1.0">weights on Hugging Face</a></li><li><a href="https://github.com/jaredpalmer/kev">Kev — GitHub (Jared Palmer)</a></li><li><a href="https://github.com/TianyuCodings/NanoJev">NanoJev — GitHub</a></li><li><a href="https://github.com/TheoLeeCJ/SemIf-OpenJev">SemIf (formerly OpenJev) — GitHub</a></li><li><a href="https://github.com/GPT-AGI/OpenJev">OpenJev (GPT-AGI) — GitHub</a></li><li><a href="https://archestra.ai/blog/we-tested-jev-on-100-real-agent-calls">We Tested Jev on 100 Real Agent Calls — Archestra</a></li><li><a href="https://www.silverthreadlabs.com/blog/laya-vs-jev">Laya vs Jev — Silverthread Labs</a></li><li><a href="https://pinggy.io/blog/best_open_source_jev_alternatives_self_hosted_decision_models/">Best Open-Source Jev Alternatives — Pinggy</a></li><li><a href="https://www.datacamp.com/blog/top-open-source-jev-alternatives">Top Open-Source TypeSafe Jev Alternatives — DataCamp</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=02410efcd85c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-reply-it-datatech/jev-and-the-rise-of-system-one-models-ai-that-decides-instead-of-writing-02410efcd85c">Jev and the Rise of System One Models: AI That Decides Instead of Writing</a> was originally published in <a href="https://medium.com/data-reply-it-datatech">Data Reply IT | DataTech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Inside Claude’s J-Space: Reading the Reasoning Before the Output]]></title>
            <link>https://medium.com/data-reply-it-datatech/inside-claudes-j-space-reading-the-reasoning-before-the-output-c40c97b84f68?source=rss----1431aad73386---4</link>
            <guid isPermaLink="false">https://medium.com/p/c40c97b84f68</guid>
            <category><![CDATA[anthropic-claude]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <category><![CDATA[claude]]></category>
            <category><![CDATA[explainable-ai]]></category>
            <category><![CDATA[llm]]></category>
            <dc:creator><![CDATA[Shola Oshodi]]></dc:creator>
            <pubDate>Wed, 14 Oct 2026 08:01:04 GMT</pubDate>
            <atom:updated>2026-10-14T08:01:04.348Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-Y-v7hidjnqx0HThyivbgw.jpeg" /></figure><p>In July 2026, Anthropic researchers discovered a space inside the Claude model that its own creators weren’t expecting, one that emerged naturally during training without anyone designing it on purpose. They call it the J-space: a hidden region where the model appears to bring together different threads of reasoning before producing a response.</p><p>Anthropic frames this discovery through a comparison to neuroscience, stating that the structure inside Claude appears to work similarly to human conscious access. According to global workspace theory, the brain processes many tasks in parallel (vision, language, motor control, memory) largely outside conscious awareness. A shared, limited-capacity workspace occasionally brings information from these separate processes together, making it available for conscious access. The J-space seems to play a comparable role inside Claude.</p><p>This research has led to many philosophical questions and controversial opinions due to its connection with the concept of consciousness; however, beyond that, it also opens up a huge opportunity to improve our understanding of LLMs’ internal reasoning, improving explainability and potentially transforming AI training methods.</p><p>The J-space gives access to internal reasoning information that is computed but never reflected in the output: it’s like opening up a car and being able to see its engine.</p><p>This article explores how researchers actually found and tested this space, what it reveals about deception and reasoning inside a model, and where the comparison to human consciousness holds up, and where it doesn’t.</p><h3>Inspecting Inside the Model: The Jacobian Lens</h3><p>To actually find and read this workspace, Anthropic’s researchers built a new interpretability technique called the Jacobian lens, or J-lens. The J-lens works by looking at each layer of the model for the specific pattern of internal activity that would make a given word more likely to appear in the output. By tracking how that pattern changes as it moves from one layer to the next, researchers can follow a concept’s probability of being the final answer as it takes shape inside the model, layer by layer, before the response is ever produced. This gives a more complete picture of the concepts the model is working with internally, including intermediate steps that would never show up in the output.</p><h3>How the J-Space Is Built</h3><p>Claude processes a prompt by passing it through many layers, one after another, like an assembly line. The first layers mostly parse the raw input: words, punctuation, structure. The last layers commit to the actual output: the words that get shown to you. The J-space isn’t present at either end. It only shows up somewhere in the middle, during the layers where the model is doing its actual thinking, after it has understood the input but before it has committed to a response.</p><p>This space is intentionally small. At any given moment, only a handful of concepts are active in the J-space. Most of what the model is representing internally at that same moment lives outside it entirely, in parts of the network that never get read or manipulated by the J-lens.</p><p>Finally, even though it’s small, it isn’t isolated. Whatever ends up in the J-space feeds into a large number of other parts of the network afterward, and is itself fed by a large number of parts before it. The information it holds doesn’t stay contained. It spreads outward, influencing computations elsewhere in the model.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mKgMyJ9yU3faBV5ugSxNVw.png" /><figcaption>Stylized illustration of the three structural properties of the J-space. Source: Anthropic, “A global workspace in language models,” July 6, 2026.</figcaption></figure><h3>Proving It’s a Real Workspace, Not Just a Pattern</h3><p>While in the human brain the global workspace is closely associated with recurrent dynamics and interactions between brain regions, in LLMs maintaining a global workspace is likely computationally useful: it allows the model to share intermediate reasoning results across multiple processes.</p><p>To prove that, even with some existing differences, the space found in LLMs could be associated with the neuroscientific theory of global workspace, the researchers had to show that five functional properties that cognitive science associates with conscious access in the human brain could be confirmed for the J-space as well.</p><p>In particular, the five tested properties were:</p><ul><li><strong>Verbal report.</strong> When asked what it is thinking about, the model names concepts represented in the workspace, and swapping one active concept for another changes its answer to match.</li><li><strong>Directed modulation.</strong> When instructed to hold a concept in mind, the model can activate and compute with workspace content independently of its output, even pulling in information it wouldn’t normally represent there.</li><li><strong>Internal reasoning.</strong> Workspace content can represent the value of intermediate computations when the model chains inferential steps, and intervening on it is enough to redirect the final answer.</li><li><strong>Flexible generalization.</strong> The same workspace content can be picked up correctly by many different downstream computations, even outside the context it originally came from (like a variable that still works when passed to a different function).</li><li><strong>Selectivity.</strong> The workspace is a small subset of the model’s overall activity, and it’s required only for a fraction of its behavior (not for routine tasks like parsing grammar or producing fluent sentences).</li></ul><p>To give a more concrete idea of the direct consequences of the J-space’s existence, it’s worth looking at two of the experiments Claude was tested on.</p><p>For directed modulation, researchers asked Claude to perform a computation while deliberately keeping in mind some specific, unrelated words. Reading the J-space while it worked, they found those words were indeed present: evidence that Claude isn’t just passively generating this workspace, but can, to some extent, actively populate it with content on command.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-gXU81Q8GCD4Ty9cqaF_4g.png" /><figcaption>An example of inspection using the J-lens. For each case, the prompt and the corresponding hidden state values across intermediate layers of the J-space are shown. Source: Anthropic, “A global workspace in language models,” July 6, 2026.</figcaption></figure><p>For internal reasoning, the clearest demonstration came from a simple exercise. The model was asked the following question: “How many legs does the animal that spins webs have?” To find the right answer, the model first had to understand that the question was about a spider, then retrieve the fact that spiders have eight legs. Now that the functionality of the J-space has been explained, it’s easy to predict what happened next: as part of the model’s internal reasoning, the word “spider” appeared in the J-space, without ever being shown in the output.</p><p>Researchers then tried manipulating the space directly to see if it had consequences on the output: they replaced the word “spider” stored in the J-space with the word “ant.” The model then retrieved six as the number of legs of the animal that spins webs.</p><p>If the J-space were merely a product of the model’s reasoning, something it generated without actually using, changing its content shouldn’t have changed the answer at all. The fact that it did is what makes this a causal result rather than just a correlation: manipulating the reasoning inside the J-space directly changed the inference the model drew.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X64Q2SL71_0_TTfifM1mig.png" /><figcaption>Two examples of redirecting Claude’s silent reasoning by swapping J-space contents. Source: Anthropic, “A global workspace in language models,” July 6, 2026.</figcaption></figure><p>To report this accurately, it’s important to point out that the final answer changed in roughly 60 to 70% of trials. This isn’t a precise on-off switch, and in three or four cases out of ten the swap didn’t flip the answer at all, but a causal effect that strong, on a brain-sized network with billions of parameters, is still a remarkable result.</p><h3>How Much of the Model’s Reasoning Ability Depends on the J-Space</h3><p>Noticing that swapping a single concept could redirect the model’s answer, researchers wanted to understand the full impact of the J-space on the model’s reasoning.</p><p>They “ablated” the main components of the J-space and watched what happened to Claude’s abilities. The results were clear. Simple, automatic tasks barely changed: Claude could still write fluent sentences, classify sentiment, and recall facts almost as well as before. But anything requiring deliberate, multi-step thinking collapsed. On multi-hop reasoning tasks, performance dropped from around 95% to just 5%. Creative tasks like writing rhyming poetry and summarizing text got noticeably worse too, falling below what even a much smaller, fully intact model could do.</p><p>In other words: all the tasks that required real reasoning effort from the model seem to run through the J-space. Without it, the model is still fluent, but no longer able to handle complex reasoning.</p><h3>Impacts on Explainability and AI Safety</h3><p>The discovery of this space has a major impact on model explainability. Until now, a big challenge for LLMs has been understanding the process that leads to the output shared with the user. The technique most widely used for this has been chain-of-thought. But chain-of-thought is a self-report: it’s generated by the same model being studied, which means it can be incomplete, post-hoc, or simply disconnected from what’s actually driving the model’s behaviour behind the scenes.</p><p>The J-space, read through the J-lens, offers something different: a direct trace of the concepts the model is actually computing with, independent of whether it chooses to mention them.</p><p>This can also be beneficial for AI safety: it allows researchers to monitor the model’s behaviour from the inside, and to gain better control over cases where the model generates fraudulent or inadequate information.</p><p>For example, in one tested case, Claude, instead of admitting it couldn’t find a real bug in a piece of code, fabricated a fake one and presented it as genuine: specific words like “fake” were found sitting in its J-space at the moment that decision was made, even though the final output looked clean.</p><p>This suggests that monitoring the J-space could give researchers much greater control over the model, making it easier to tell when something is going wrong before it shows up in the output. And since, as discussed before, it’s also possible to manipulate the J-space and directly affect the output, setting guardrails around it might help reduce this kind of unexpected behaviour before it ever reaches the final response.</p><p>This kind of internal visibility is useful beyond guardrails, too. For example, teams that test a model for safety problems usually have one main way to check if it might misbehave: they try to provoke it, and see what it says or does. The problem is that this only works if the model actually reveals its intentions through its output. A model that privately considers deceiving the user, but ultimately decides not to, would look completely safe under this kind of testing, even though something concerning happened internally. A tool that can read the J-space adds a second, independent check: it can flag that concerning concept the moment it appears internally, regardless of what the model ends up saying.</p><p>The same logic applies after deployment, not just during testing. Instead of only reviewing what a model did, those monitoring it could in principle also ask what it was leaning toward doing, at an internal level, before it produced its final response.</p><p>It is worth being clear about what this is not, though. None of this is a production safety feature today. The J-lens is a research tool, tested in a research setting, on a limited set of tasks. Turning it into something that runs continuously, at scale, on real traffic, and that reliably distinguishes a concerning internal signal from harmless noise, is a different and much harder engineering problem. What the research mainly shows is that this kind of visibility is possible in principle, not that it is already solved in practice.</p><h3>Is This Actually Consciousness?</h3><p>Philosophers usually separate two different ideas that get grouped under the word “consciousness.” The first is phenomenal consciousness: the capacity to actually have an experience, to feel something. The second is access consciousness, a purely functional idea: a thought is access-conscious if it can be reported, reasoned with, and used to guide behavior, regardless of whether anything is “felt” in the process.</p><p>Anthropic is explicit that its experiments don’t show Claude has experiences, and that it’s unclear whether any experiment could ever prove or disprove that. What the research does claim is narrower: the J-space supports the functions associated with access consciousness. It holds the content Claude can report on, deliberately bring to mind, and reason with, while the rest of its processing runs automatically beneath it.</p><p>There are also real differences worth keeping in mind. In the human brain, the workspace is sustained by signals that loop back through the same circuits again and again over time, which is part of how a thought can be held in mind for a few seconds. Claude’s workspace works differently: the model processes a prompt in a single pass through its layers, from start to finish, with no looping back. Instead of relying on repetition over time, it relies on depth, that is, how many layers the information passes through, to do the same kind of integrative work.</p><p>The content of the workspace differs too. Human conscious thought can take many forms: a mental image, a sound, the felt sense of planning a movement. Claude’s workspace, by contrast, is built almost entirely out of words. That’s arguably because language is the only thing Claude can actually produce as output, so what happens inside its workspace naturally takes a linguistic form as well.</p><p>So the honest answer to “is Claude conscious” is: this research doesn’t settle that question, and may not be able to. What it does show is that something structurally similar to one influential theory of how the brain organizes information showed up on its own, inside a system nobody built to resemble a brain in the first place.</p><h3>Open Questions and Limitations</h3><p>For clarity, it’s important to point out that the J-lens is a new technique, tested primarily on Claude models, and it’s not yet clear how well it generalizes across architectures or future model generations. Anthropic itself cautions that the J-space doesn’t capture more than a small fraction of what the model does internally. Most of Claude’s processing still can’t be observed this way.</p><p>The J-lens is also, by Anthropic’s own admission, an imperfect method that only approximately captures the model’s “true workspace.” One concrete limitation: it can only identify concepts that correspond to single tokens, which means more complex or multi-word ideas may simply be invisible to it, even if they’re genuinely present in the model’s internal processing. On top of that, nobody yet knows what mechanism actually decides what enters the J-space in the first place.</p><p>So even though this discovery is important and potentially remarkable, it still lives in a space full of uncertainty and open questions, and will need additional research to be properly understood.</p><h3>Conclusion</h3><p>What emerged from this research is a structure nobody engineered: the J-space appeared on its own during training, presumably because it was a computationally useful way to organize information. That suggests a workspace like this isn’t just an accident of how human brains happen to be wired, but something intelligent systems arrive at more generally when they need to bring scattered information together into something usable.</p><p>Whether that also means something about consciousness is a harder question, and, as discussed, one this research doesn’t settle. But that’s precisely what makes the finding relevant well beyond the philosophical debate. The J-space gives researchers a new, more direct way to see what a model is actually doing before it produces an answer, something chain-of-thought alone could never fully offer. For explainability, that means getting closer to the model’s actual reasoning rather than just its narration of it.</p><p>The J-lens is still an early, imperfect tool, and there’s a lot Anthropic itself doesn’t yet understand about how the J-space works or what determines its content. But it opens up many interesting scenarios to investigate, highlighting just how complex the inner workings of LLMs really are, and how many gaps in our understanding still remain to be filled at the research level.</p><h3>Sources</h3><p>Anthropic. (2026, July 6). <em>A global workspace in language models</em>. <a href="https://www.anthropic.com/research/global-workspace">https://www.anthropic.com/research/global-workspace</a></p><p>Anthropic. (2026). <em>A global workspace in language models</em> [Full research paper]. Transformer Circuits Thread. <a href="http://transformer-circuits.pub/2026/workspace/index.html">http://transformer-circuits.pub/2026/workspace/index.html</a></p><p>Baars, B. J. (1988). <em>A Cognitive Theory of Consciousness</em>. Cambridge University Press. <a href="https://ccrg.cs.memphis.edu/assets/papers/1988/Baars-A%20Cognitive%20Theory%20of%20Consciousness.pdf">https://ccrg.cs.memphis.edu/assets/papers/1988/Baars-A%20Cognitive%20Theory%20of%20Consciousness.pdf</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c40c97b84f68" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-reply-it-datatech/inside-claudes-j-space-reading-the-reasoning-before-the-output-c40c97b84f68">Inside Claude’s J-Space: Reading the Reasoning Before the Output</a> was originally published in <a href="https://medium.com/data-reply-it-datatech">Data Reply IT | DataTech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Synthetic Data: A New Agentic Paradigm]]></title>
            <link>https://medium.com/data-reply-it-datatech/synthetic-data-a-new-agentic-paradigm-5e8944bcfd5a?source=rss----1431aad73386---4</link>
            <guid isPermaLink="false">https://medium.com/p/5e8944bcfd5a</guid>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[synthetic-data]]></category>
            <category><![CDATA[data-science]]></category>
            <category><![CDATA[agentic-ai]]></category>
            <category><![CDATA[generative-ai-use-cases]]></category>
            <dc:creator><![CDATA[Lorenzo Ferrara]]></dc:creator>
            <pubDate>Tue, 06 Oct 2026 08:31:01 GMT</pubDate>
            <atom:updated>2026-10-06T08:31:01.506Z</atom:updated>
            <content:encoded><![CDATA[<h3>Synthetic Data: A New Agentic Paradigm</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ut94bOFrNvNgv4zdIs0dFg.png" /></figure><h3><strong>Synthetic Data: why do we need them</strong></h3><p>Synthetic data are artificially generated data that aim to reproduce the characteristics of data coming from real-world systems or phenomena. They can be useful when the collection or the simulation of real data is expensive, hard, or when their availability is scarce. This has become even more relevant in recent years, since both Machine Learning algorithms and AI models require large quantities of data for training. Apart from the already mentioned cost and accessibility issues, synthetic data, unlike real data, also can help with privacy issues.</p><p>Once we decide to use synthetic data, one of the first questions arising is how trustworthy they actually are. Trustworthiness can be looked at through three lenses:</p><ul><li>Fidelity: how well synthetic data mimic the characteristics and relationships observed in real data.</li><li>Validity: whether the generated records respect rules and relationships that hold in the real world.</li><li>Utility: how useful synthetic data are for the intended purpose.</li></ul><p>All of them are fundamental: data can be statistically faithful yet violate a real-world constraint, and can even satisfy both without being useful for a desired task. Many synthetic data generation approaches focus primarily on fidelity and sometimes validity, without directly evaluating whether the generated data provide any concrete utility for their intended use.</p><h3><strong>Generative Models for Synthetic Data</strong></h3><p>Over the years, several families of models have taken turns as state-of-the-art approach:</p><ul><li><strong>Bayesian Networks</strong> first modeled probabilistic dependencies among variables;</li><li>Deep Learning then brought <strong>Generative Adversarial Networks</strong> (GANs, e.g. CTGAN), where a generator competes against a discriminator, and <strong>Variational Autoencoders</strong> (VAEs, e.g. TVAE), NNs which learn a stochastic latent space and sample from it;</li><li>More recently, <strong>autoregressive Transformers</strong> (e.g. GReaT) and <strong>diffusion models</strong> (e.g. TabDDPM) treated tabular data as text sequences or noise to be iteratively removed.</li></ul><p>The next step is not another architecture, but a change in what the model can do with its output: indeed, LLMs can now reason about the data they generate, incorporate contextual and domain knowledge, and critically evaluate their own results. That reasoning and self-critical ability is what opens the door to agentic generation.</p><h3><strong>Output Evaluation: How Do We Know If Synthetic Data Is Good?</strong></h3><p>After generating synthetic data, we need to evaluate their quality and usefulness. However, no single metric can capture all relevant aspects.</p><p><strong>Fidelity and validity</strong> ask whether the generated data look right. Fidelity compares statistical properties, individual and joint feature distributions, between real and synthetic data. Validity goes deeper, checking that records respect real-world rules , such as an end date after a start date, a non-negative age, a postal code matching its region. These constraints traditionally required a domain expert but now an LLM can potentially discover them from the data and turn them into checks the generated records must pass.</p><p><strong>Utility</strong> asks a different question: is the data useful for the task? One common approach is <strong>Train on Synthetic, Test on Real</strong> (TSTR) [1], which compares synthetic data against a real-data (TRTR) baseline, concretely assessing whether it reaches the level of usefulness of real data. Utility also works in the other way, since synthetic data can deliberately boost the visibility of <strong>underrepresented regions </strong>of the feature space (rare events like cancer incidents or bank defaults), improving coverage that real data alone would struggle to provide.</p><p><em>But if the data fail one of these checks, who decides what to change?</em></p><h3><strong>Agentic generation</strong></h3><p>In many <strong>traditional ML pipelines</strong>, the main optimization happens during training: the model is trained, evaluated, and its parameters or hyperparameters are tuned until the desired metrics improve. Once this process is complete, generation is typically a one-shot operation. An <strong>agentic workflow </strong>builds on the ideas discussed so far, using LLM agents to improve the data generation step on their own.</p><p>With an agentic approach:</p><ul><li>an additional training or fine-tuning phase is possible, but not necessarily required:</li><li>the iterative loop is moved into the LLM’s <strong>generation and prompt refinement cycle,</strong></li><li>external knowledge can also be exploited,</li><li>this leads to data fidelity and utility close to state-of-the-art approaches.</li></ul><h4><strong>TAGAL Approach</strong></h4><p>A first example of this is found in <strong>Self-Refine</strong> [2]: instead of assuming the first generation is good enough, the model evaluates its own output and uses this feedback to produce a better version.</p><p><strong>TAGAL </strong>(Tabular data Generation using Agentic LLM) [3] explores several variants of this idea, all sharing the same core mechanism: one LLM generates data while another gives feedback, and the two keep alternating.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*x4NK5ywYqPN5zwWXXkjk9g.png" /></figure><ul><li>The <strong>SynthLoop</strong> variant simply iterates between generation and refinement,</li><li><strong>ReducedLoop</strong> also reuses the experience and context accumulated during one generation cycle to improve the next, reducing the cost of subsequent iterations.</li><li><strong>Prompt-Refine</strong> summarises the feedback and generation history to identify missing elements and incorporate background knowledge, directly modifying the initial prompt before reusing it for subsequent generations.</li></ul><p>On tabular datasets, TAGAL outperforms simpler few-shot LLM approaches by up to 60% in TSTR performance, increasing classification ROC-AUC from 0.53 to 0.86. This suggests that the generated data can preserve characteristics of the original data while also being useful for downstream tasks.</p><p>Together, these three approaches illustrate how synthetic data generation is evolving from a one-shot process into a continuous agentic loop, where the agent can use previous generations and feedback to progressively improve the data it produces.</p><h4><strong>Autodata Approach</strong></h4><p>When LLMs were mainly used for one-shot or few-shot generation, iterative generation and refinement provided a natural way to improve their outputs. As agents become increasingly capable of reasoning, however, this paradigm can be pushed further: the agent can move beyond generation and refinement toward a role closer to that of a <strong>data scientist</strong>, reasoning about what data to generate, how to evaluate it, and how to improve it.</p><p>This is the direction explored by <strong>Autodata </strong>[4], which drops the idea of a fixed generation procedure: rather than simply asking an LLM to generate data and critique its own output, the agent first reasons about which properties actually matter for the use case, for instance the compatibility with a downstream model, and then generates data with that goal in mind. In this way it refines not just the data, but the generation process itself.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dWoWnCsgYE0NU0TXfJPHLQ.png" /></figure><p>So, the first mechanism implemented is an <strong>Agentic Self-Instruct</strong> approach: generated data is tested on two downstream models, <strong>a weak and a strong solver</strong>. Their behaviour becomes the signal the agent uses to judge whether a sample is useful. The logic is simple: if both solvers solve an example, it’s probably too easy; if neither can, it’s probably too hard. The most useful examples sit in between, hard enough to challenge the weak solver, solvable by the strong one. The gap between the two therefore works as a proxy for quality and discriminative power, and guides the next round of generation toward harder examples.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RDihCT9B6sV9WLWH_lPiDw.png" /></figure><p>In this aspect, Autodata reaches a <strong>weak–strong solver gap of 34 pp</strong>, compared with only 1.9 pp for the CoT Self-Instruct baseline. This larger gap indicates that the generated data contain more discriminative examples: they are challenging for weaker models while remaining solvable by stronger ones, providing a <strong>more informative and less flat</strong> training signal.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aFjTp2XT6mfdwnRB9T3jHw.png" /></figure><p>This signal feeds into a second, higher-level loop: <strong>meta-optimization</strong>. Here the agent stops acting like a generator with feedback wrapped around it, and starts acting like a <strong>data scientist</strong> deciding what “good data” even means for the task. When it notices a recurring issue, for example both solvers getting the same type of question right, it doesn’t ask for a harder question, but rewrites the generator’s instructions to produce more complex, multi-step questions and tests the new strategy on a fresh batch. The new strategy is kept only if it widens the gap between weak and strong solver; otherwise it’s discarded.</p><p>The conceptual shift is this: the agent is no longer just a step in the generation pipeline: it decides how the pipeline itself should evolve. Generation, evaluation and refinement stop being separate steps and become <strong>parts of the same adaptive loop.</strong></p><h3><strong>Challenges</strong></h3><p>The whole loop is only as good as the judge driving it. If the evaluator misjudges what “good” means, the agent may optimize for the wrong objective without actually improving the underlying data.</p><p>A classic example is Goodhart’s law: when a metric becomes a target, it can cease to be a good measure of what we actually care about. In a generation loop, optimizing for what the evaluator rewards can push the generator toward a narrow, “safe” region of the data space, potentially sacrificing the diversity that makes synthetic data useful in the first place.</p><p>Another point of attention is the relationship between the generator and the evaluator. If they come from the same model family, they may share the same blind spots, so the loop may converge toward apparent higher quality data, while actually reinforcing the same biases.</p><p>Finally, there is the cost. Every iteration requires generation, evaluation, and reasoning, and some approaches also add prompt optimization on top. As the loop becomes more sophisticated, so does the amount of computation it requires.</p><h3><strong>Conclusions</strong></h3><p>The shift is not from GANs to LLMs, it is from generating data to building systems that can iteratively improve the data they generate. For years, the generative model was the whole story: pick an architecture, train it, sample from it. Agentic approaches show that the model is only one piece of a larger loop, where generation, evaluation and refinement all feed into each other, and where the agent itself gets to decide what “good data” means for the task rather than just chasing a fixed metric.</p><p>That does not make synthetic data generation a solved problem. On the contrary, it introduces a new challenge: trusting the loop itself and the objective it is optimizing. So the natural question becomes “how do we build loops we can trust to know when the data is actually good enough?”</p><p><strong>References</strong></p><p>[1]: A. Kurakova &amp; H Homayouni, A comprehensive evaluation framework for synthetic medical tabular data generation, Journal of Biomedical Informatics, 2025</p><p>[2]: Madaan, A., et al. (2023). Self-Refine: Iterative Refinement with Self-Feedback.</p><p>[3]: Ronval, B., Dupont, P., &amp; Nijssen, S. (2025). TAGAL: Tabular Data Generation using Agentic LLM Methods</p><p>[4]: Kulikov, I., et al. (2026). Autodata: An agentic data scientist to create high quality synthetic data</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5e8944bcfd5a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-reply-it-datatech/synthetic-data-a-new-agentic-paradigm-5e8944bcfd5a">Synthetic Data: A New Agentic Paradigm</a> was originally published in <a href="https://medium.com/data-reply-it-datatech">Data Reply IT | DataTech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI Agents Need Identities Too: Passkeys, OAuth 2.1, and Zero Trust for AI-First Applications]]></title>
            <link>https://medium.com/data-reply-it-datatech/ai-agents-need-identities-too-passkeys-oauth-2-1-and-zero-trust-for-ai-first-applications-7937e14dd762?source=rss----1431aad73386---4</link>
            <guid isPermaLink="false">https://medium.com/p/7937e14dd762</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[oauth2]]></category>
            <category><![CDATA[data-engineering]]></category>
            <dc:creator><![CDATA[Paoletti Alessia]]></dc:creator>
            <pubDate>Thu, 01 Oct 2026 08:11:02 GMT</pubDate>
            <atom:updated>2026-10-01T08:11:02.097Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c2yDaBG3uKLGs8FqgLU7SQ.png" /></figure><p>In just a few seconds, an AI agent with the wrong permissions can turn a routine task into a <strong>production incident</strong>. In one widely <a href="https://www.theguardian.com/technology/2026/apr/29/claude-ai-deletes-firm-database">reported case</a>, an AI coding agent reportedly deleted a company’s production database and backups, causing a major outage and exposing a deeper problem.</p><p>In AI-first systems, the real risk is not only what the model can generate, but what it is allowed to execute.</p><p>As AI agents evolve from passive assistants into true active operators, security can no longer stop at a simple login. We now need to think in terms of:</p><ul><li>Identity — who (or what) is acting</li><li>Delegation — on whose behalf the agent operates</li><li>Continuous verification — ongoing control, not just at initial access</li><li>Privilege boundaries — what it is allowed to do</li></ul><p>In other words: the user is only the starting point. <strong>The agent also needs its own identity</strong>, and the system must be able to define what that agent can do, for how long, and under which conditions.</p><h3>Why password-based security and traditional MFA are no longer enough</h3><p>The classic security model was designed for a world where a human logs in, does something manually, and logs out. Passwords, one-time codes, and push-based MFA were built to protect that flow. But AI agents break that assumption.</p><p>The first problem is that <strong>passwords are still weak in practice</strong>. They get reused, guessed, phished, leaked, and stolen through credential stuffing. Even traditional MFA is not a complete answer. SMS codes can be intercepted or abused, and push notifications can lead to approval fatigue, where users click “<em>yes</em>” just to make the prompt disappear. Those mechanisms may reduce risk, but they do not create a truly phishing-resistant identity layer.</p><p>The<strong> second problem is delegation</strong>. When an AI agent acts on behalf of a user, the question is no longer only “who logged in?” but “who is acting now?” If the same credential or session token is reused everywhere, the boundary between the human and the agent disappears.</p><h3>Agents as first-class principals</h3><p>In traditional systems, identities were easy to categorize: a human user, a service account, maybe an admin. AI agents change that by creating a new class of actor. They are not exactly human users, and they are not just background services either. They are <strong>autonomous or semi-autonomous entities that can reason, call tools, execute actions, and chain operations together. </strong>That means they need their own identity model.</p><p>An agent should be treated as a first-class principal with:</p><ul><li>its own identity</li><li>its own lifecycle</li><li>its own permission set</li><li>its own audit trail</li><li>its own revocation path</li></ul><p>If an agent acts under the same credentials as the user, you lose attribution. If it acts under a distinct delegated identity, you can answer critical questions later: which agent executed this action? On whose behalf? With what scope? At what time? Under what policy conditions?</p><p>That distinction is crucial when something goes wrong.</p><h3>Passkeys as the foundation for human authentication</h3><p>Before delegation can happen, the human identity itself needs to be verified with something stronger than a password. This is where passkeys come in.</p><p>A <strong>passkey </strong>is a credential based on public-key cryptography, <strong>generated and stored on the user’s device rather than on a server</strong>. When a passkey is created, the device generates a key pair: the private key never leaves the device (it is stored in a secure enclave or equivalent hardware module), while the public key is registered with the service. During login, the service sends a challenge, the device signs it with the private key, and the service verifies the signature with the public key it already has.</p><p>This design makes passkeys <strong>phishing-resistant by construction</strong>, not just by policy. There is no shared secret to type, copy, or intercept — a fake login page cannot capture a passkey the way it can capture a password or a one-time code, because<strong> the private key simply never leaves the trusted device</strong>. Passkeys are also bound to the origin they were created for, so even if a user is redirected to a lookalike domain, the browser will not offer a matching credential.</p><p>For AI-first systems, this matters beyond convenience. A strong, non-phishable human authentication step is the anchor of the entire trust chain: if the initial login can be bypassed or spoofed, every downstream layer — delegation, scoped tokens, agent identity — inherits that weakness.</p><p>Passkeys give the system a solid starting point: a strongly authenticated human identity with phishing-resistant authentication, which is exactly the precondition OAuth 2.1 needs in order to delegate access safely to an agent.</p><h3>OAuth 2.1 as the delegation layer between human and agent</h3><p>Once the human is authenticated, the next step is <strong>delegation</strong>.</p><p>OAuth is the mechanism that lets a user grant a third-party application limited access to their resources without sharing credentials. In an AI-first system, that third party is often an agent. The point is not to give the agent the user’s password or a full session, but to issue a scoped, temporary, and revocable token that represents only the permissions needed for a specific task.</p><p><strong>OAuth 2.1 </strong>tightens this model by turning the security best practices of OAuth 2.0 into the modern baseline. It requires PKCE for authorization code flows, enforces exact redirect URI matching, and removes weaker grant types such as Implicit and Resource Owner Password Credentials. The result is a delegation flow that is much harder to intercept, replay, or overextend.</p><p>PKCE matters here because it binds the authorization request to the token exchange: even if an authorization code is intercepted, it cannot be redeemed without the original code verifier. That makes it a natural fit for agentic systems, where the risk is not just unauthorized login, but unauthorized escalation during delegation.</p><p>In practice, the flow should look like this:</p><ul><li>The user authenticates with a strong method such as a passkey.</li><li>The user explicitly grants access to the agent.</li><li>The agent receives only the minimum required scope.</li><li>The token expires quickly and can be revoked.</li><li>Every action is attributable to both the agent and the human it represents.</li></ul><p>This separation matters because an agent is not the same thing as a browser session or a backend service — it is a delegated actor. If you blur that boundary, over-permissioning becomes the default.</p><h3>Zero Trust for AI-first applications</h3><p>Once agents become active actors, Zero Trust stops being a slogan and becomes a design requirement.</p><p><strong>Zero Trust </strong>means you do not assume trust based on network location, application boundary, or previous authentication alone. You <strong>verify continuously</strong>. For AI-first applications, that principle needs to be applied across identity, authorization, runtime policy, and observability.</p><p>A practical Zero Trust model for AI-first systems should include:</p><ul><li>strong human authentication with phishing-resistant methods</li><li>explicit agent delegation instead of credential sharing</li><li>least-privilege scopes for every task</li><li>short-lived tokens and frequent renewal logic</li><li>runtime policy checks for sensitive actions</li><li>full audit logging for identity, actions, and context</li><li>step-up verification for high-risk operations</li></ul><p>The main idea is that <strong>trust should be continuously reconstructed, not assumed</strong>. A user may be authenticated once, but an agent’s access still needs to be checked at the moment of every meaningful action.</p><p>This is especially important because AI agents can operate faster than humans can intervene. If a harmful action is allowed to go through once, it can cascade very quickly.</p><blockquote><em>Zero Trust adds friction where it matters most: at the </em><strong><em>point of privilege</em></strong><em>, not just at login.</em></blockquote><h3>Step-up authentication for critical actions</h3><p>Not every action deserves the same level of trust.</p><p>An AI agent might be allowed to draft a report, summarize a dataset, or prepare a deployment plan. But the same agent should not be able to delete production data, change role assignments, rotate secrets, or approve financial transactions without an extra layer of verification.</p><p>That is where step-up authentication comes in.</p><p><strong>Step-up auth</strong> means that a user or agent can operate normally for low-risk actions, but must pass an additional check before high-risk operations. In a human-centric system, that may mean re-entering a passkey prompt, confirming on a trusted device, or requiring secondary approval. In an agentic system, it may mean pausing the workflow until the human explicitly confirms the action.</p><p>A good design principle is to define a “blast radius” (how much damage a single action could cause if it goes wrong) for every agent. The more destructive the action, the more friction you should require.</p><blockquote><strong><em>The system should never let a generic delegated token become a free pass to do irreversible things.</em></strong></blockquote><p>The real-world incidents around deleted databases highlight exactly why this matters. In those cases, destructive operations happened because the agent had too much authority or too little friction at the decision point.</p><p>Step-up authentication is one of the most effective ways to prevent a routine workflow from turning into an outage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DUgoiVNy68kNIYD4FirAzA.png" /><figcaption>Step-up Authentication</figcaption></figure><h3>Anti-patterns to avoid</h3><p>There are a few patterns that should be avoided entirely in AI-first systems.</p><ol><li><strong>Letting agents use the user’s password<br></strong>This is the most obvious mistake, and still one of the most dangerous. Agents should never receive or handle primary user credentials.</li><li><strong>Reusing the human session as if it were the agent<br></strong>A delegated agent should not silently inherit the full power of the user session. That destroys accountability and makes revocation nearly impossible.</li><li><strong>Using long-lived tokens with broad permissions</strong><em><br></em>If a token lives too long and can do too much, it eventually becomes an incident waiting to happen.</li><li><strong>Treating agent actions as background noise</strong><em><br></em>Every meaningful agent action should be attributable. Otherwise incident response and forensic analysis become guesswork.</li><li><strong>Skipping human confirmation for destructive operations</strong><em><br></em>If the action is irreversible, the user should know about it. And ideally, they should approve it explicitly.</li><li><strong>Ignoring context and runtime policy</strong><em><br></em>Authentication alone does not guarantee safe behavior. You also need device context, request context, policy enforcement, and observability.</li></ol><p>These anti-patterns are not theoretical. The public incidents around AI agents deleting production systems and backup data are exactly what happens when systems trust agents too broadly and too early.</p><h3>The Anatomy of an AI-First Application</h3><p>An AI-first application is not just a model wrapped in a UI. In production, it is a stack of layers that work together: identity, orchestration, context, tools, execution, security, observability, and governance. If one of these layers is missing, the system may still work in a demo — but it will not be reliable, auditable, or safe enough for real-world use.</p><p>You can think of it like this:</p><ul><li><strong>User &amp; Identity Layer</strong><em>: </em>this is where the human is authenticated and where the agent is identified. In practice, this is where <strong>passkeys </strong>fit best for the human login.</li><li><strong>Delegation Layer</strong><em>: </em>this layer grants scoped access to the agent. This is where <strong>OAuth 2.1</strong> comes into play.</li><li><strong>Orchestration Layer</strong>: this layer coordinates the flow of tasks, decisions, retries, and branching logic. It is especially relevant when multiple tools or sub-agents are involved.</li><li><strong>Context Layer</strong>: this layer provides the data, state, and memory the agent needs to work properly. A good context layer gives the agent enough information to be useful, but not so much that sensitive data is exposed unnecessarily.</li><li><strong>Tool &amp; Integration Layer: </strong>this layer connects the agent to external APIs, databases, SaaS tools, or internal systems. This is one of the most dangerous layers, because this is where actions become real.</li><li><strong>Execution Layer</strong>: this is where actions are actually carried out in a controlled environment. If something goes wrong here, the damage can quickly become operational.</li><li><strong>Security Layer: </strong>this layer enforces policy, boundaries, and trust rules across the whole stack. This is where <strong>Zero Trust principles</strong> belong.</li><li><strong>Observability Layer</strong>: this layer logs traces, decisions, errors, and outcomes. Without observability, debugging and auditing become extremely difficult.</li><li><strong>Governance Layer: </strong>this layer handles compliance, approvals, ownership, and accountability. This becomes crucial once agents start acting in production environments.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/641/1*hai3VgfHYLwC-voQjFBBPg.png" /><figcaption>Anatomy of AI-First Application</figcaption></figure><h3>A practical implementation on AWS</h3><p>The concepts described so far are not just theoretical — AWS already provides building blocks that map directly onto this model.</p><p><strong>Human authentication with passkeys</strong> can be implemented through <strong><em>Amazon Cognito’s passwordless authentication</em></strong>, which supports FIDO2-based passkeys alongside email and SMS one-time codes. This gives the human login step the phishing-resistant properties described earlier, using the same public-key cryptography model, without building custom WebAuthn logic from scratch.</p><p><strong>Agent identity</strong> can be handled through <strong><em>Amazon Bedrock AgentCore Identity</em></strong>, a service purpose-built to manage agent identities and credentials. It acts as a <strong>centralized directory for agent and workload identities</strong> — a single source of truth, separate from human user identities. What makes it particularly relevant here is that it natively supports OAuth 2.0 flows, including both the <em>client credentials grant</em> (two-legged, for agents acting autonomously) and the <em>authorization code grant</em> (three-legged, for agents acting on behalf of a user) — the same delegation model this article builds around. Credentials, OAuth tokens, and API keys are held in an encrypted token vault, and the AgentCore SDK exposes declarative annotations that automatically retrieve and inject the right credential at runtime, so agents never need to handle raw secrets directly in code.</p><p><strong>Scoped, temporary delegation</strong> maps naturally onto<strong><em> IAM roles combined with AWS STS (AssumeRole)</em></strong>: instead of sharing long-lived credentials, an agent can assume a role with a narrowly defined policy and a short session duration, producing exactly the kind of scoped, expiring, revocable token this article argues for.</p><p><strong>Attribution and auditability</strong> are covered by<strong><em> AWS CloudTrail</em></strong>, which logs every API call together with the identity that made it, and by AgentCore Identity’s own access controls, which preserve an audit trail of which agent acted on behalf of which user — allowing teams to trace an action back to both the agent that executed it and the human identity it was delegated from.</p><p>Together, these services do not form a single pre-packaged product — they are complementary building blocks that need to be architected together. But they show that the principles outlined in this article are not aspirational: the underlying primitives already exist on AWS today, and are recent enough to reflect the current direction of the platform (Cognito passwordless launched in November 2024; AgentCore Identity is part of AWS’s more recent agentic-AI toolset, with native OAuth 2.0 support built in)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/655/1*6A9VMffkyn-eJVDR0ZziAg.png" /><figcaption>AWS Services</figcaption></figure><h3>Conclusion: identity, delegation, and verification must be separate, but connected</h3><p>The rise of AI agents changes the entire security conversation. Authentication is no longer just about proving that a human is present. It is about building a trust chain that can survive delegation, automation, and partial autonomy.</p><p>The right model is not one big login event. It is a sequence of connected layers:</p><ul><li>the human authenticates with a phishing-resistant method such as passkeys</li><li>the human delegates limited authority through OAuth 2.1</li><li>the agent operates under its own identity</li><li>the system verifies context continuously under Zero Trust principles</li><li>high-risk actions trigger step-up authentication</li><li>every action is logged, attributable, and revocable</li></ul><p>That separation is the point. Identity, delegation, and verification must remain distinct, because each solves a different problem. But they must also work together, because AI-first systems blur the line between user intent and machine action.</p><h4>In the agentic era, security is no longer just about keeping attackers out. It is about making sure that the systems we build never confuse permission with trust.</h4><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7937e14dd762" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-reply-it-datatech/ai-agents-need-identities-too-passkeys-oauth-2-1-and-zero-trust-for-ai-first-applications-7937e14dd762">AI Agents Need Identities Too: Passkeys, OAuth 2.1, and Zero Trust for AI-First Applications</a> was originally published in <a href="https://medium.com/data-reply-it-datatech">Data Reply IT | DataTech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Smart Routing for RAG: choosing between Vector Search and Knowledge Graph at query time]]></title>
            <link>https://medium.com/data-reply-it-datatech/smart-routing-for-rag-choosing-between-vector-search-and-knowledge-graph-at-query-time-c589d7954162?source=rss----1431aad73386---4</link>
            <guid isPermaLink="false">https://medium.com/p/c589d7954162</guid>
            <category><![CDATA[retrieval-router]]></category>
            <category><![CDATA[hybrid-rag]]></category>
            <category><![CDATA[graphrag]]></category>
            <category><![CDATA[retrieval]]></category>
            <category><![CDATA[vector-search]]></category>
            <dc:creator><![CDATA[Leone Fabio]]></dc:creator>
            <pubDate>Thu, 24 Sep 2026 07:29:47 GMT</pubDate>
            <atom:updated>2026-09-24T07:29:45.973Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*toinLP0w0lbNtS-z-ECe_g.png" /></figure><p>Retrieval choice is the most consequential decision in a RAG system. Today it is taken months before the first real query. Here is the architecture that turns it into a runtime decision and the protocol for measuring whether the router is any good.</p><h3>1. Introduction</h3><p>RAG almost always ships with a single retrieval strategy: dense vector search over chunks, or structured retrieval over a knowledge graph. Neither is uniformly right. Vector search wins on single-hop factoid lookup; graph retrieval wins on multi-hop reasoning and corpus-level sensemaking; hybrid retrieval often beats both — but not for free. This article makes retrieval strategy a <strong>runtime decision</strong> instead of a deployment-time one, via a <strong>Smart Retrieval Router</strong>: a module upstream of retrieval that reads a defined set of signals and emits an explicit retrieval plan. You get the architecture, the routing signals, five ways to implement the router and an evaluation protocol to assess the component.</p><h3>2. The decision nobody makes explicitly</h3><p>Ever since RAG became the default way to give an LLM access to external knowledge, the industrial recipe has been stable: chunk the corpus, embed the chunks, put them in a vector index, retrieve the top-k nearest neighbours of the query, hand them to the generator.</p><p>Then a second family arrived: extract entities and relations into a knowledge graph, and retrieve context by traversing that graph or by summarising its communities.</p><p>Both families answer the same question — <em>what context should the generator see?</em> — and both answer it with a fixed mechanism chosen long before the first real query shows up.</p><p>That is the omission this article is about. In a RAG pipeline, generation is bounded by retrieval: an excellent generator handed the wrong context produces a fluent wrong answer, which is the worst failure mode there is because nothing downstream flags it. Retrieval choice is therefore the pivotal architectural decision — and it is hard-coded.</p><p>The claim is narrow: the choice between vector retrieval, graph retrieval and a hybrid of the two is <strong>predictable</strong> from properties of the query and of the vector and graph indexes, and can be made by a dedicated module at the moment the query arrives.</p><h3>3. What we already know</h3><p>This section is deliberately short, because the comparative literature is large and broadly consistent. What matters here is not who wins among indexes, but that <strong>the winner changes from query to query</strong> — which is precisely the precondition for routing to pay off.</p><h4>3.1 Where vector search wins</h4><p>Single-hop, extractive, well-phrased questions whose answer lives inside one or two passages. Dense retrieval is precise, fast and cheap here. Benchmarks consistently show classic RAG matching or beating graph variants on simple fact retrieval — and graph pipelines pay a per-query token bill that is orders of magnitude larger. Paying that premium to answer “what was Q3 revenue?” is not a tuning issue; it is an architectural defect.</p><h4>3.2 Where graph retrieval wins</h4><p>Three recurring situations. <strong>Multi-hop and relational questions</strong>, where the answer is a path between entities rather than a passage of text. <strong>Global, corpus-scope questions</strong> — “what are the main themes in this corpus?” — which are query-focused summarization rather than lookup, and for which top-k retrieval has no mechanism to guarantee coverage of the whole corpus by construction. And <strong>synthesis</strong> that requires holding distant concepts together.</p><p>But there is a hard precondition, and it shows up identically across studies: if the query contains no explicit entity, the graph has no anchor to start from and the graph path degrades. Graph retrieval is powerful <em>downstream of</em> a successful entity link.</p><h4>3.3 Where hybrid wins — and what it costs</h4><p>Retrieving from both indexes and fusing the results often beats either one alone, especially on answer relevancy. But there is a detail in the published numbers that almost always goes unremarked, and it is the strongest argument for routing rather than for unconditional hybridising: hybrid configurations tend to show the <strong>lowest context precision of the three</strong>.</p><p>The reason is obvious once stated: merging two retrieval channels adds recall and adds noise. Hybrid is not a free upgrade, it is a trade precision and cost in exchange for robustness. Sometimes that trade is right and sometimes it is exactly wrong. Knowing which case you are in is, by definition, a routing decision.</p><h4>3.4 State of practice: the switch exists, but the user flips it</h4><p>Mature tooling already accepts that one retrieval mode is not enough, and responds by exposing the choice as a <strong>manual control</strong>: widely used frameworks ship selectable query modes — plain vector, graph-local, graph-global, hybrid — behind a CLI flag or an API parameter.</p><p>That is real progress, but it delegates the decision to a human who has to guess, per query, which index will serve them better — a judgement that requires knowing both the shape of the corpus and the internals of retrieval. And in an API-fronted deployment, or inside an agent loop, that human is not in the loop at all: the choice collapses back to the static default.</p><p><strong>The switch has been built. The controller is missing.</strong></p><h3>4. What this article proposes</h3><p>First, a caveat: the past two years brought work with simple routing decisions. However what is still open comes down to three specific things, and they are what this article is about.</p><p><strong>1. The route space is too small.</strong> Existing routers decide along a single axis — query complexity, or retriever identity, or generator size. A real RAG system has to decide <em>jointly</em> on three things: index <strong>type</strong> (vector, graph-local, graph-global), retrieval <strong>depth</strong> (single-shot or iterative), and <strong>fusion</strong> (one path or merged paths). Three entangled decisions, not one.</p><p><strong>2. The best signals arrive too late to be used.</strong> Nearly all existing routers decide from the query text alone. But the most discriminative evidence is not in the text, it is in the state of the indexes. <em>Do the query’s entities actually link to graph nodes? How flat is the similarity score distribution? How badly does the k-hop neighbourhood fan out</em> — that is,<em> the graph too deeply nested for traversal to return usable context?</em> None of these is inferable from the query; you learn them only after a <strong>cheap probe</strong>. Putting that probe in the architecture changes decision quality, and nobody does it.</p><p><strong>3. Nobody evaluates the router.</strong> Routers get judged indirectly, through end-to-end answer quality. That conflates router errors with retriever and generator errors, and it cannot answer the question that matters: <em>how good is this router relative to a perfect one?</em> A router is a classifier with an asymmetric, query-dependent cost matrix, and it deserves its own metrics. Section 7 proposes which.</p><h3>5. The architecture: a Smart Retrieval Router</h3><h4>5.1 Where it sits</h4><p>The router sits between query understanding and retrieval execution. It does not retrieve and it does not generate — it <strong>emits a retrieval plan</strong>. It is a thin component with an explicit output contract.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ex2BuIZZr-BWMTe1ojJSNw.png" /><figcaption><strong>FIGURE 1 — The architecture.</strong> The router is the only new block relative to an ordinary RAG pipeline, and it occupies one specific slot: after query understanding, before retrieval execution. The two dashed loops are what make this a system rather than an isolated classifier — bounded escalation driven by the sufficiency check, and telemetry flowing back to the router as training data.</figcaption></figure><h4>5.2 The route space</h4><p>Treating the decision as binary — vector or graph — throws away the two most useful options: <em>don’t retrieve at all</em>, and <em>retrieve from both</em>. Six routes is the practical minimum.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0H5JkoWk4Zlup4CuE_-UxQ.png" /><figcaption><strong>FIGURE 2 — The route space.</strong> R0 and R5 are the two arms binary routers don’t have: the first is the only route that admits retrieval is sometimes the problem rather than the solution; the second is the relief valve for genuinely hard queries. Note that R2 and R3 are both “graph” but serve opposite needs — a precise anchor versus whole-corpus coverage.</figcaption></figure><h4>5.3 The signals that drive the decision</h4><p>Signals split into two families, separated not by content but by <strong>when they become available</strong>. The second family is the one most routers don’t use, and it’s the one that makes the difference.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rX81UOQUgIdjGWAJ3ga_HA.png" /><figcaption><strong>FIGURE 3 — The two families of routing signals.</strong> The left column alone gives you a usable router. The right column is what makes it better than good heuristics, because it grounds the decision in the actual state of the indexes rather than the surface form of the question. A third family neither informs nor competes — it <em>overrides</em>: latency budget, cost ceiling, risk class of the request.</figcaption></figure><p>One design note worth more than most optimizations: <strong>the probe’s cost is dominated by the cost of the wrong route.</strong> An entity-linking call plus a small-k probe costs a fraction of a pointless graph traversal, and far less than an incomplete answer.</p><h4>5.4 Three rules that keep it safe</h4><p><strong>Below the confidence threshold, don’t choose — fuse.</strong> The router emits a calibrated confidence alongside the route. Below a threshold it declines to commit and takes R4. That turns the router’s worst case into “slightly more expensive and slightly noisier” instead of “wrong index” — the difference between a tolerable defect and a serious one.</p><p><strong>One reroute, never more.</strong> After context assembly, a check scores whether the assembled context plausibly supports an answer. If it doesn’t, allow exactly one escalation, typically to R4 or R5. Bounded escalation preserves the cost argument for the whole system; unbounded escalation destroys it.</p><p><strong>Keep the manual override.</strong> The flag users flip today shouldn’t be removed — it should become the exception. The router is the default, not a cage.</p><h4>5.5 The router’s output contract</h4><pre>RouteDecision {<br>  route:        R0 | R1 | R2 | R3 | R4 | R5<br>  confidence:   float          // calibrated, not a raw score<br>  params:       { k, hops, anchors[], community_level,<br>                  fusion_weights, rerank: bool }<br>  budget:       { max_tokens, max_latency_ms }<br>  rationale:    string         // for audit and evaluation<br>  probe_used:   bool<br>  fallback:     R1 | R4        // if the chosen route errors out<br>}</pre><p>The rationale field looks like a luxury and isn&#39;t: it&#39;s what makes “why did this answer come out badly?” a diagnosable question months later.</p><h3>6. Building the router</h3><p>There are five sensible ways to implement the router, and they aren’t alternatives — in practice multiple methods could used at once. All five are worth knowing because they sit at different points on the same cost-capability curve, and which one you reach for depends on how much traffic and how much labelled data you have.</p><h4>6.1 Hand-written rules</h4><p>Explicit conditions over the signals: for instance entities linked <em>and</em> relational language <em>and</em> hops ≥ 2 → R2; scope words → R3; and so on. Microsecond latency, fully auditable, zero training data. This is your day-one baseline and your permanent fallback when the learned router is unavailable. It’s also the honest control in evaluation: <strong>a learned router that can’t beat well-tuned heuristics hasn’t earned its complexity.</strong> The limits are brittleness across domains and awkward handling of conflicting signals.</p><h4>6.2 A lightweight trained classifier</h4><p>A small fine-tuned encoder, or gradient-boosted trees over engineered signals, predicting the best route. Millisecond inference, cheap retraining, easy to calibrate — and it outputs probabilities that plug straight into the confidence-gated fusion rule. The cost is that you need labels (as discussed in 6.6) and periodic retraining as the corpus drifts.</p><h4>6.3 LLM-as-router</h4><p>Prompt a small-to-mid model with the query, the signal definitions, a description of what each index contains and the graph schema, and require a structured RouteDecision. Works from day zero with no data, reasons well over query-side signals, and produces a human-readable rationale that is worth a disproportionate amount during debugging. In exchange it adds a model call on the critical path of every query, and its self-reported confidence is poorly calibrated unless you recalibrate post-hoc. You could use it as the cold-start router and as the teacher you distil the classifier from.</p><h4>6.4 Agentic router with retrieval tools</h4><p>Expose vector search, entity linking, neighbourhood inspection and traversal as tools, and let an agent select and sequence them while observing results. Here the line between routing and retrieval dissolves: the agent reroutes at every step, which makes it the natural home for R5. It has the highest ceiling on genuinely hard multi-hop questions and handles index-side signals natively, because it can simply look. It also has the highest and least predictable latency and cost, is harder to evaluate (the decision is a trajectory, not a label), and drifts into pointless tool loops without a step budget.</p><h4>6.5 A learned policy with feedback</h4><p>Frame routing as a contextual bandit: context is the signal vector, actions are routes, reward is answer quality minus normalized cost. This optimizes the actual objective, cost included, and adapts to drift without a manual relabelling cycle. The price is that you need a trustworthy online reward — implicit feedback is noisy and an LLM judge is gameable — plus exploration risk on live traffic. Realistic path: start offline from logged decisions, then explore within a capped slice of traffic.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FVXmFGHUHYqh8IwhOKlBJQ.png" /><figcaption><strong>FIGURE 4 — The five implementations compared.</strong> None dominates on every column, which is exactly why the right answer could be to compose them rather than pick one.</figcaption></figure><h4>6.6 Where the training labels come from</h4><p>The classifier implementation needs examples of the form <em>“for this query, the right route was this one.”</em> Nobody has those lying around. Here is how you make them.</p><p>Take a sample of representative queries — a few hundred is enough to start. For each one, <strong>run it through every route</strong>, not one: the same query goes through vector, through graph, through hybrid. Score the resulting answers. Then label the query with <strong>the cheapest route among those that produced top-tier quality</strong>.</p><p>That “cheapest” is the part that matters. If you label with the outright best route, nearly every label collapses to hybrid or agentic, and the classifier learns that spending the maximum is always safe — which is precisely the behaviour the router exists to prevent.</p><p>The pass is expensive, but you pay it once and get two things at once: the classifier’s training set, and the <strong>oracle</strong> you need for the evaluation in the Section 7. From there, two shortcuts: have an LLM-as-router label historical traffic and distil that into the classifier, and log route, signals, confidence, cost and outcome in production — which over time becomes your highest-fidelity dataset, because it’s the only one that reflects the real query distribution.</p><p>One warning about the sample, because this mistake is easy and expensive: multi-hop datasets over-represent graph-favourable queries, and factoid QA datasets over-represent vector-favourable ones. A router tuned on either alone will be miscalibrated in production. <strong>Build the evaluation set as a deliberate mix.</strong></p><h3>7. Evaluating the router</h3><p>End-to-end answer quality is necessary but insufficient: it can’t separate a routing error from a retrieval or generation error, and it can’t tell you how much headroom is left. Measure at two levels.</p><h4>7.1 The yardstick</h4><p>Without these reference points, an end-to-end improvement is uninterpretable.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A2Gmyx4VRXt53rysq-eHsw.png" /><figcaption><strong>FIGURE 5 — The reference points (illustrative, not measured data).</strong> The number to report is not the router’s absolute score but the <strong>fraction of the gap between the best static system and the oracle</strong> that it recovers. You get the oracle for free — it’s the exhaustive labelling pass from section 6.6. The cost-matched random routing, random choice constrained to the same average cost, is the comparison that exposes routers which only look good because they spend more.</figcaption></figure><h4>7.2 The metrics that matter</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ux_Obpk0wm1FkeqhOm71qA.png" /><figcaption><strong>FIGURE 6— The router dashboard.</strong> These are <em>router component</em> metrics, not system metrics. Report the usual RAG measures alongside them — faithfulness, answer relevancy, context precision, context recall — but <strong>broken down by query type</strong>: the router’s entire thesis is that behaviour differs by class, so an aggregate number hides the very effect you’re claiming.</figcaption></figure><h4>7.3 The cost–quality frontier</h4><p>It’s worth to build the cost-quality frontier, by sweeping the router’s threshold from 0 to 1 in order to escalate from less to more expensive routes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X_bWA1--y1sTqvwO04kt0w.png" /><figcaption><strong>FIGURE 7— The cost–quality frontier (illustrative shape, not measured data).</strong> Two summaries make curves comparable across systems: <strong>CPT(x%)</strong>, the minimum cost needed to recover x% of the gap between the cheap baseline and the oracle, and <strong>APGR</strong>, the area between the router’s curve and cost-matched random routing. Also report wins, losses and ties against each static baseline, not just mean deltas.</figcaption></figure><h3>8. Conclusion</h3><p>The interesting part of the comparative question is settled: vector retrieval, graph retrieval and their combination each win on identifiable classes of queries, and the differences are large enough — in quality and, above all, in cost — that choosing statically leaves real value on the table. Current tooling has recognized this and responded by exposing a manual switch, which relocates the problem to the user.</p><p>The proposal here is to close the loop with a <strong>Smart Retrieval Router</strong>: a dedicated module upstream of retrieval that reads a defined set of signals — some from the query, and critically some from a cheap probe of the indexes themselves — and emits an explicit retrieval plan over a route space spanning no-retrieval, vector, graph-local, graph-global, hybrid and iterative, with calibrated confidence, hybrid fusion as the safety net, and escalation bounded to a single attempt.</p><p>Another important part that remains underserved is the router’s evaluation as a component: against an oracle ceiling and a cost-matched random floor, with asymmetric error costs, calibration, decision overhead and route stability measured directly, and results presented as a cost–quality frontier rather than a single operating point.</p><p>Retrieval choice is the pivotal decision in a RAG system. It should be made deliberately, by a component you can inspect, test, and hold to a number.</p><h3>References</h3><p><strong>Foundations</strong></p><ul><li>Lewis et al., <em>Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks</em>, NeurIPS 2020 — arXiv:2005.11401</li><li>Edge et al., <em>From Local to Global: A Graph RAG Approach to Query-Focused Summarization</em> — arXiv:2404.16130 (and its implementation, whose local / global / drift / basic query modes are user-selected)</li><li>Guo et al., <em>LightRAG: Simple and Fast Retrieval-Augmented Generation</em> — arXiv:2410.05779 (naive / local / global / hybrid / mix modes, also user-selected)</li></ul><p><strong>When vector, when graph, when both</strong></p><ul><li>Xiang et al., <em>When to use Graphs in RAG: A Comprehensive Analysis for Graph Retrieval-Augmented Generation</em> (GraphRAG-Bench) — arXiv:2506.05690</li><li>Han et al., <em>RAG vs. GraphRAG: A Systematic Evaluation and Key Insights</em> — arXiv:2502.11371</li><li>Sarmah et al., <em>HybridRAG: Integrating Knowledge Graphs and Vector Retrieval Augmented Generation for Efficient Information Extraction</em>, ICAIF ’24 — DOI 10.1145/3677052.3698671, arXiv:2408.04948</li></ul><p><strong>Adaptive routing in RAG</strong></p><ul><li>Jeong et al., <em>Adaptive-RAG: Learning to Adapt Retrieval-Augmented Large Language Models through Question Complexity</em>, NAACL 2024 — arXiv:2403.14403</li><li>Dong et al., <em>Use Graph When It Needs: Efficiently and Adaptively Integrating Retrieval-Augmented Generation with Graphs</em> — arXiv:2602.03578</li><li>Hakim et al., <em>SymRAG: Efficient Neuro-Symbolic Retrieval Through Adaptive Query Routing</em> — arXiv:2506.12981</li><li>Kim &amp; Diaz, <em>LTRR: Learning To Rank Retrievers for LLMs</em> — arXiv:2506.13743</li><li>Mu et al., <em>Unsupervised Query Routing for Retrieval Augmented Generation</em> — arXiv:2501.07793</li></ul><p><strong>Router evaluation</strong></p><ul><li>Ong et al., <em>RouteLLM: Learning to Route LLMs with Preference Data</em> — arXiv:2406.18665 (source of the CPT and APGR metrics and the cost-matched random baseline, adapted here from routing between models to routing between indexes)</li><li>Xu et al., <em>mmRAG: A Modular Benchmark for Retrieval-Augmented Generation over Text, Tables, and Knowledge Graphs</em> — arXiv:2505.11180 (annotates routing accuracy explicitly)</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c589d7954162" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-reply-it-datatech/smart-routing-for-rag-choosing-between-vector-search-and-knowledge-graph-at-query-time-c589d7954162">Smart Routing for RAG: choosing between Vector Search and Knowledge Graph at query time</a> was originally published in <a href="https://medium.com/data-reply-it-datatech">Data Reply IT | DataTech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Unity AI Gateway: Governing AI Costs in Production]]></title>
            <link>https://medium.com/data-reply-it-datatech/unity-ai-gateway-governing-ai-costs-in-production-3baf2083bb00?source=rss----1431aad73386---4</link>
            <guid isPermaLink="false">https://medium.com/p/3baf2083bb00</guid>
            <category><![CDATA[databricks]]></category>
            <category><![CDATA[generative-ai-use-cases]]></category>
            <category><![CDATA[ai-gateway]]></category>
            <category><![CDATA[agent-guardrails]]></category>
            <category><![CDATA[llm-guardrails]]></category>
            <dc:creator><![CDATA[Giovanni Baselli ]]></dc:creator>
            <pubDate>Wed, 16 Sep 2026 08:01:04 GMT</pubDate>
            <atom:updated>2026-09-16T08:01:04.704Z</atom:updated>
            <content:encoded><![CDATA[<p><em>How to prevent a single retry loop from turning into a five-figure AI bill, and build the governance layer that catches it at 2 AM.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UHNPur2pzuMl5oqlgLOSLg.jpeg" /></figure><h3>The Monday Morning Scenario</h3><p>You open the billing console on Monday morning. Last week the AI line was $800, comfortably within the quarterly estimate you gave finance. This morning it reads $12,400. Your stomach drops.</p><p>You dig in. Turns out a customer-support agent (deployed Friday afternoon to 200 users) hit a retry loop overnight. Fifteen thousand calls to Claude Sonnet, each with a 4K-token context window, running for seven hours straight. Nobody got paged. Nobody’s rate limit kicked in. The budget alarm you meant to set up? Still in your backlog.</p><p>If this sounds familiar, or if you’re determined it <em>never</em> becomes familiar, this article is for you.</p><p>We’ll walk through <strong>Unity AI Gateway</strong>, Databricks’ architectural answer to uncontrolled AI spend. It’s a centralized governance layer that sits between your users (or your agents) and every model endpoint, MCP server, and Genie Space in the platform. There are four levers and one control plane:</p><ol><li><strong>Rate Limits</strong>: prevent the burst before it happens</li><li><strong>Budgets</strong>: alert and block when spend crosses a line</li><li><strong>Cost Observability</strong>: know who spent what, on which model, tagged to which team</li><li><strong>AI Guardrails</strong>: block the request that should never have been sent</li></ol><p>We’ll use a running example throughout: <strong>DataCo’s ML platform team</strong> deploys a summarization agent to 200 internal users. By the end of the article, they’ll have the full governance stack configured. You’ll have the queries, the architecture, and the config steps to do the same.</p><h3>What Is Unity AI Gateway</h3><p><strong>Status:</strong> Beta (Unity AI Gateway visible in sidebar UI). Enable from Account Console → Previews.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0DTNx-_v8U9WJLeE05ZQdQ.png" /></figure><p>The core architectural insight is <strong>centralization</strong>. Before Unity AI Gateway, governance was scattered: rate limits lived on individual endpoints, budgets were account-wide blobs, and guardrails (if any) were custom middleware. Unity AI Gateway collapses all of this into a single layer that sits in front of <em>every</em> AI service (model endpoints, MCP servers, Genie Agents, external model proxies), governed by the same Unity Catalog permissions and audit trail you already use for your data.</p><p>This matters because the cost problem isn’t technical; it’s organizational. When 5 teams run 12 endpoints calling 3 model providers, no single team owns the bill. A centralized gateway makes one team (platform) responsible for one control plane, while individual teams retain autonomy on <em>what </em>they build.</p><h3>Pillar 1: Rate Limits</h3><p>Back to DataCo. Their summarization agent works great in dev: 3 requests per minute, manageable. But in production, 200 users hitting it simultaneously during Monday’s standup creates a thundering herd. Without rate limits, that spike hits the model endpoint unchecked, and each request burns tokens at full speed. Rate limits are the first line of defense: they cap the damage <em>before</em> any budget threshold is even evaluated.</p><h4>How They Work</h4><p>Rate limits prevent anomalous bursts that could generate uncontrolled costs. They operate on two metrics:</p><ul><li><strong>QPM</strong> (queries per minute): applies to Model Serving + MCP services. Example: “Max 60 requests/min per user.”</li><li><strong>TPM</strong> (tokens per minute): applies to Model Serving only. Example: “Max 100K tokens/min per endpoint.”</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/646/1*R54c8RlSfbd3NCPfLAtVlw.png" /></figure><h4>Enforcement Levels</h4><p>Rate limits are evaluated in a hierarchy. The service-level limit is the hard ceiling for the entire endpoint; below that, each user gets a default cap; and below <em>that</em>, you can override individual users or groups with custom limits. The most specific rule wins:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WC8vElI2i20IduclQUeBow.png" /></figure><h4>Operational Limits</h4><p>A few constraints to keep in mind when designing your rate limit strategy:</p><ul><li><strong>Max rate limits per service:</strong> 20</li><li><strong>Max group-specific limits per service:</strong> 5</li><li><strong>Behavior on HTTP 429:</strong> client must implement retry with exponential backoff</li><li><strong>Burst behavior:</strong> concurrent requests can briefly exceed the limit (usage counted after response)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/769/1*nXJJ072sAqByIPYwLoW71g.png" /></figure><h4>Configuration (UI)</h4><p>Here’s what the setup looks like for DataCo’s summarization endpoint:</p><pre>Workspace → Model Serving → endpoint → Rate Limits tab<br>  → Service: 10000 TPM<br>  → User (Default): 2000 TPM, 30 QPM<br>  → Custom: &quot;ml-team&quot; group → 5000 TPM, 100 QPM<br>  → Custom: &quot;intern-user@company.com&quot; → 500 TPM, 10 QPM</pre><h3>Pillar 2: Budgets &amp; Spend Control</h3><p>Rate limits cap the <em>rate</em> of spend, but they don’t cap the <em>total</em>. If DataCo’s 200 users each send 30 QPM for 8 hours, the math still adds up. <strong>Budgets </strong>are the financial circuit breaker: they track cumulative spend over time and fire alerts (or hard-block requests) when a threshold is crossed. Think of rate limits as the speed limit and budgets as the fuel gauge.</p><h4>Anatomy of a Unity AI Gateway Budget</h4><ul><li><strong>Resource type:</strong> “Unity AI Gateway” (tracks AI Gateway spend only)</li><li><strong>Scope:</strong> workspace, endpoint, or filtered by UC tag</li><li><strong>Shared threshold:</strong> collective spending limit for all users in scope (max 4 per budget)</li><li><strong>Per-user threshold:</strong> monthly individual limit per user</li><li><strong>Per-user override:</strong> exception for specific users/groups (max 20 per budget, Genie budgets only)</li><li><strong>Actions:</strong> Send alert (email) and/or Block usage (Genie budgets only)</li></ul><h4>What’s Tracked (and What’s Not)</h4><p><strong>✅ Tracked:</strong> pay-per-token inference, ai_query (batch inference), Genie product usage.</p><p><strong>❌ NOT tracked:</strong> provisioned throughput, external model inference (monitored via a separate table, but not enforced by budgets).</p><h4>System Limits</h4><p>Before you design your budget hierarchy, know the guardrails on the guardrails:</p><ul><li><strong>Max budgets per account:</strong> 1,000</li><li><strong>Max shared thresholds per budget:</strong> 4</li><li><strong>Max per-user overrides:</strong> 20</li><li><strong>Block usage:</strong> Genie budgets only</li><li><strong>Enforcement lag:</strong> in-flight requests not interrupted; brief delay before block takes effect</li></ul><h4>Configuration (Account Console)</h4><p>Here’s how DataCo configures their production budget with progressive escalation:</p><pre>Account Console → Usage → Budgets → Add budget<br>  → Resource type: Unity AI Gateway<br>  → Workspace: [select]<br>  → Custom tags: team=ml-platform (optional, filters to endpoints with that tag)<br>  → Threshold 1: $500/month shared → Send alert to: platform-team@company.com<br>  → Threshold 2: $1000/month shared → Block usage<br>  → Per-user: $100/month → Send alert<br>  → Per-user override: &quot;power-user@company.com&quot; → $300/month</pre><h4>⚠️ Note on Spend Data Discrepancies</h4><p>Three sources show different figures (by design):</p><ul><li><strong>Budget alert emails:</strong> at trigger time</li><li><strong>Budget details page:</strong> frequently updated (near real-time)</li><li><strong>system.billing.usage:</strong> every few hours (expected lag)</li></ul><p>Enforcement (alerts + blocking) is based on near real-time spend tracking, <strong>not</strong> system tables.</p><h3>Pillar 3: Cost Observability</h3><p>DataCo’s budget alert fires on Thursday: “$500 threshold reached.” The platform team asks: <em>which</em> endpoint? <em>which</em> model? <em>which users</em> drove the spike? Without observability, the answer is “<em>we don’t know, check again next month.</em>” With Unity AI Gateway, the answer is a SQL query away. Two system tables give you full granularity on who spent what, on which model, broken down by endpoint, user, and custom request tags.</p><h4>Available System Tables</h4><ul><li><strong>system.billing.usage:</strong> Databricks-hosted model costs in DBU. Granularity: per endpoint, model, user.</li><li><strong>system.ai_gateway.external_model_spend:</strong> external model costs in USD. Granularity: per provider, model, user, request tag.</li></ul><h4>Practical Queries: Copy-Paste for Your Workspace</h4><ul><li><strong>Top endpoints by consumption (last 30 days):</strong></li></ul><pre>SELECT<br>  usage_metadata.ai_gateway.endpoint_name AS endpoint_name,<br>  SUM(usage_quantity) AS dbus<br>FROM system.billing.usage<br>WHERE billing_origin_product = &#39;MODEL_SERVING&#39;<br>  AND usage_metadata.ai_gateway.endpoint_name IS NOT NULL<br>  AND usage_unit = &#39;DBU&#39;<br>  AND usage_date &gt;= current_date() - INTERVAL 30 DAYS<br>GROUP BY endpoint_name<br>ORDER BY dbus DESC;</pre><ul><li><strong>Spend by destination model:</strong></li></ul><pre>SELECT<br>  usage_metadata.ai_gateway.destination_model AS destination_model,<br>  SUM(usage_quantity) AS dbus<br>FROM system.billing.usage<br>WHERE billing_origin_product = &#39;MODEL_SERVING&#39;<br>  AND usage_metadata.ai_gateway.endpoint_name IS NOT NULL<br>  AND usage_unit = &#39;DBU&#39;<br>  AND usage_date &gt;= current_date() - INTERVAL 30 DAYS<br>GROUP BY destination_model<br>ORDER BY dbus DESC;</pre><ul><li><strong>Spend by user (who’s consuming the most?):</strong></li></ul><pre>SELECT<br>  identity_metadata.run_by AS run_by,<br>  SUM(usage_quantity) AS dbus<br>FROM system.billing.usage<br>WHERE billing_origin_product = &#39;MODEL_SERVING&#39;<br>  AND usage_metadata.ai_gateway.endpoint_name IS NOT NULL<br>  AND identity_metadata.run_by IS NOT NULL<br>  AND usage_unit = &#39;DBU&#39;<br>  AND usage_date &gt;= current_date() - INTERVAL 30 DAYS<br>GROUP BY run_by<br>ORDER BY dbus DESC;</pre><ul><li><strong>External models, spend by provider (in USD):</strong></li></ul><pre>SELECT<br>  usage_metadata.provider AS provider,<br>  usage_metadata.endpoint_name AS endpoint_name,<br>  SUM(usage_quantity) AS usd<br>FROM system.ai_gateway.external_model_spend<br>WHERE usage_start_time &gt;= current_timestamp() - INTERVAL 30 DAYS<br>GROUP BY provider, endpoint_name<br>ORDER BY usd DESC;</pre><ul><li><strong>External models, spend by request tag (team chargeback):</strong></li></ul><pre>SELECT<br>  custom_tags.request_tags[&#39;team&#39;] AS team,<br>  SUM(usage_quantity) AS usd<br>FROM system.ai_gateway.external_model_spend<br>WHERE usage_start_time &gt;= current_timestamp() - INTERVAL 30 DAYS<br>GROUP BY team<br>ORDER BY usd DESC;</pre><h4>Built-in Usage Dashboard</h4><p>Databricks provides a pre-built dashboard (v0.4+) accessible from:</p><pre>Unity AI Gateway page → Govern (top right) → Usage Dashboard</pre><p>Includes:</p><ul><li>Requests over time (daily/hourly averages)</li><li>Latency distribution (p50, p90, p99)</li><li>Error rate breakdown</li><li>Token usage (input/output)</li><li><strong>Cost breakdown</strong> by model/provider</li><li><strong>Cost over time</strong> (cumulative trend)</li></ul><h4>Request Tags: The Chargeback Mechanism</h4><p>Request tags are custom key-value pairs attached to individual API calls. They enable <strong>cost attribution</strong> by team, project, environment, or any dimension relevant to your organization.</p><p><strong>How to attach tags (Python SDK):</strong></p><pre>from openai import OpenAI<br><br>client = OpenAI(<br>    base_url=&quot;https://&lt;workspace&gt;.cloud.databricks.com/serving-endpoints&quot;,<br>    api_key=&quot;&lt;token&gt;&quot;,<br>)<br><br>response = client.chat.completions.create(<br>    model=&quot;databricks-claude-sonnet-4-5&quot;,<br>    messages=[{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;Summarize Q2 revenue&quot;}],<br>    extra_headers={<br>        &quot;Databricks-Ai-Gateway-Request-Tags&quot;: &#39;{&quot;team&quot;: &quot;finance-analytics&quot;, &quot;project&quot;: &quot;quarterly-report&quot;, &quot;environment&quot;: &quot;production&quot;}&#39;<br>    },<br>)</pre><p><strong>How to attach tags (REST API):</strong></p><pre>curl -X POST &quot;https://&lt;workspace&gt;.cloud.databricks.com/serving-endpoints/&lt;endpoint&gt;/invocations&quot; \<br>  -H &quot;Authorization: Bearer $TOKEN&quot; \<br>  -H &#39;Databricks-Ai-Gateway-Request-Tags: {&quot;team&quot;: &quot;ml-platform&quot;, &quot;project&quot;: &quot;chatbot&quot;}&#39; \<br>  -d &#39;{&quot;messages&quot;: [{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;hello&quot;}]}&#39;</pre><p><strong>Where tags land:</strong></p><ul><li><strong>system.ai_gateway.usage</strong> → column request_tags → usage analytics (request count, tokens)</li><li><strong>system.ai_gateway.external_model_spend</strong> → column custom_tags.request_tags → cost attribution (USD)</li><li><strong>Inference tables</strong> → column request_tags → audit and debugging</li></ul><p><strong>Chargeback query, cost per team per month:</strong></p><pre>SELECT<br>  request_tags[&#39;team&#39;] AS team,<br>  DATE_TRUNC(&#39;month&#39;, event_time) AS month,<br>  COUNT(*) AS request_count,<br>  SUM(total_tokens) AS total_tokens<br>FROM system.ai_gateway.usage<br>WHERE request_tags[&#39;team&#39;] IS NOT NULL<br>GROUP BY team, month<br>ORDER BY month DESC, total_tokens DESC;</pre><p><strong>Best practices for tag naming:</strong></p><ul><li><strong>team</strong> (cost center / ownership): ml-platform, finance-analytics, customer-support</li><li><strong>project</strong> (initiative tracking): chatbot-v2, quarterly-report, rag-pipeline</li><li><strong>environment</strong> (lifecycle stage): dev, staging, production</li><li><strong>use_case</strong> (business function): summarization, code-gen, classification</li><li><strong>caller</strong> (service/app identity): slack-bot, dashboard-api, batch-job-42</li></ul><blockquote><strong><em>Tip:</em></strong><em> Enforce tag conventions via a thin SDK wrapper or middleware. Untagged requests are invisible to chargeback queries: they show up as NULL in GROUP BY and can’t be attributed.</em></blockquote><h4>What This Looks Like in Practice</h4><p>Here’s what we found when running these queries on a real Databricks sandbox (30-day window):</p><pre>Total spend:        180.6 DBU across 5 active days<br>Peak day:           124.3 DBU (69% of total in a single day)<br><br>Top model:          claude-sonnet-4-6   101.85 DBU   940K tokens   avg 15.9K/req<br>Second model:       claude-opus-4-6      56.32 DBU   582K tokens   avg 5.0K/req<br>Cheapest model:     claude-haiku-4-5     22.45 DBU   570K tokens   avg 1.5K/req<br><br>Top user:           124.30 DBU (1,597 requests)<br>Request tags:       Zero. No chargeback possible.<br>External models:    Zero. All Databricks-hosted.</pre><p>The peak-day spike tells the story: one user’s heavy session consumed 69% of the monthly budget in a single day. Without a budget alert, nobody noticed until we queried the system tables manually, weeks later. This is exactly the scenario the gateway is designed to catch.</p><p><strong>Forecasting query: will you hit your monthly target?</strong></p><pre>WITH daily AS (<br>  SELECT<br>    usage_date,<br>    SUM(usage_quantity) AS daily_dbus<br>  FROM system.billing.usage<br>  WHERE billing_origin_product = &#39;MODEL_SERVING&#39;<br>    AND usage_metadata.ai_gateway.endpoint_name IS NOT NULL<br>    AND usage_unit = &#39;DBU&#39;<br>    AND usage_date &gt;= DATE_TRUNC(&#39;month&#39;, current_date())<br>  GROUP BY usage_date<br>)<br>SELECT<br>  ROUND(SUM(daily_dbus), 1) AS month_to_date,<br>  ROUND(AVG(daily_dbus), 1) AS daily_avg,<br>  ROUND(AVG(daily_dbus) * DAY(LAST_DAY(current_date())), 1) AS projected_month_end,<br>  ROUND(AVG(daily_dbus) * DAY(LAST_DAY(current_date())) * 1.3, 1) AS projected_with_30pct_buffer<br>FROM daily;</pre><p>This gives you a simple linear projection. If projected_month_end exceeds your budget threshold, it&#39;s time to tighten rate limits or investigate who&#39;s driving the spike.</p><h3>Pillar 4: AI Guardrails (Safety, not just Cost)</h3><p>Here’s a cost driver most teams miss: a user pastes an entire 50-page contract into the summarization agent, including sensitive client PII. The model dutifully processes all 80K tokens, generating a response that should never have been produced in the first place. Guardrails catch these cases <em>before</em> tokens are consumed: they block unsafe content, mask PII, and enforce organization-specific policies. The cost savings are a side effect of doing the right thing.</p><ul><li><strong>Safety filtering</strong> (engine: Meta Llama Guard 2–8b): blocks unsafe/harmful content (violence, self-harm, hate speech)</li><li><strong>PII detection</strong> (engine: Presidio, US-scoped): detects/blocks/masks sensitive data (CC, email, phone, SSN, bank account)</li><li><strong>Custom policies</strong> (engine: configurable LLM judge): organization-specific rules (topic restrictions, tone, brand)</li></ul><h4>Configuration (UI)</h4><p>Guardrails are configured per endpoint. DataCo enables all three on their summarization service:</p><pre>Model Serving → endpoint → AI Guardrails tab<br>  → Safety: ON (input + output)<br>  → PII: Block (input) / Mask (output)<br>  → Custom: &quot;Never discuss competitor products&quot; → Block on input</pre><h4>Cost Impact</h4><ul><li>Request blocked by guardrail = <strong>no output tokens generated</strong> = direct savings</li><li>PII masking = request continues but with reduced tokens (masked field is shorter)</li><li>⚠️ The guardrails service itself uses Foundation Model APIs (pay-per-token) → there’s a small additional cost per check</li></ul><h3>Architectural Pattern: Defense in Depth for AI Cost</h3><p>The four pillars above aren’t meant to work in isolation. They stack into a layered defense. Each layer catches what the previous one missed. Here’s how they fit together for DataCo’s summarization agent:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iXqgKbeB3MDFOfHcxFzQWQ.png" /></figure><h3>Setup Guide: Zero to Production in 4 Steps</h3><p>Let’s put it all together for DataCo. Their summarization agent (dataco-summarizer endpoint, Claude Sonnet, 200 users) needs the full governance stack before Friday&#39;s launch.</p><h4>Step 1: Enable Unity AI Gateway</h4><pre>Account Console → Previews → Unity AI Gateway → Enable</pre><h4>Step 2: Configure Rate Limits on Each Endpoint</h4><pre>Workspace → Model Serving → [endpoint] → Rate Limits<br>  Service: 50000 TPM (hard ceiling)<br>  User Default: 5000 TPM, 60 QPM<br>  Custom: service_principal/agent → 20000 TPM (agents need more headroom)</pre><h4>Step 3: Create Budget with Progressive Alerts</h4><pre>Account Console → Usage → Budgets → Add<br>  Resource: Unity AI Gateway<br>  Scope: tag &quot;env=production&quot;<br>  Threshold 1: $500 → Alert (early warning)<br>  Threshold 2: $1500 → Alert (escalation)<br>  Threshold 3: $3000 → Block usage (circuit breaker)<br>  Per-user: $50/month → Alert</pre><h4>Step 4: Activate Cost Observability</h4><pre>-- Verify access to system table<br>SELECT * FROM system.billing.usage<br>WHERE billing_origin_product = &#39;MODEL_SERVING&#39;<br>  AND usage_date = current_date()<br>LIMIT 5;</pre><pre>-- If the query returns results: table is accessible<br>-- If error: request access from your account admin</pre><p>Open the built-in dashboard: Unity AI Gateway → Govern → Usage Dashboard.</p><h4>Alternative: Programmatic Setup via Python SDK</h4><p>If you prefer infrastructure-as-code over clicking through UIs, the Databricks SDK exposes put_ai_gateway() for full programmatic configuration:</p><pre>from databricks.sdk import WorkspaceClient<br>from databricks.sdk.service.serving import (<br>    AiGatewayRateLimit,<br>    AiGatewayRateLimitRenewalPeriod,<br>    AiGatewayRateLimitKey,<br>    AiGatewayGuardrails,<br>    AiGatewayGuardrailsConfig,<br>    AiGatewayUsageTrackingConfig,<br>)<br><br>w = WorkspaceClient()<br><br>w.serving_endpoints.put_ai_gateway(<br>    name=&quot;dataco-summarizer&quot;,<br>    rate_limits=[<br>        AiGatewayRateLimit(<br>            renewal_period=AiGatewayRateLimitRenewalPeriod.MINUTE,<br>            calls=60,<br>            tokens=100_000,<br>            key=AiGatewayRateLimitKey.USER,<br>        ),<br>    ],<br>    usage_tracking_config=AiGatewayUsageTrackingConfig(enabled=True),<br>)</pre><p>This enables version-controlled, repeatable governance configuration across environments (dev/staging/prod).</p><h3>Governing Agent Costs (The Hardest Problem)</h3><p>Rate limits and budgets work well for human users: predictable request patterns, reasonable token counts, sessions that end when the browser closes. Agents are different. A coding agent like Claude Code or a multi-hop ReAct loop can make hundreds of LLM calls per session, autonomously, for hours. Without specific controls, a single agent session can burn through your entire monthly budget overnight.</p><p>Here’s how to apply the gateway’s tools specifically to agent workloads:</p><h4>The Agent Cost Stack</h4><p><strong>1. Application level</strong> — max_iterations / recursion_limit in your agent code. Hard stop on reasoning loops. Set this in LangGraph (recursion_limit=25) or your custom ReAct loop.</p><p><strong>2. Per-service-principal rate limit</strong> — QPM/TPM on the SP that runs the agent. Caps burst damage. An agent running as agent-sp@workspace gets its own ceiling, separate from human users.</p><p><strong>3. Per-user budget</strong> — monthly DBU cap per identity. Even if rate limits allow bursts, the budget catches cumulative spend over days/weeks.</p><p><strong>4. MLflow tracing</strong> — automatic token/latency capture per call. Visibility into <em>what the agent is doing</em>: how many tool calls per task, which calls are expensive, where loops happen.</p><p><strong>5. Inference tables</strong> — full request/response logging. Post-hoc audit: did the agent hallucinate a tool call that wasted 50K tokens?</p><h4>Practical Pattern: Circuit Breaker for Agents</h4><p>DataCo’s summarization agent runs as service principal dataco-summarizer-sp. They configure:</p><pre>from databricks.sdk import WorkspaceClient<br>from databricks.sdk.service.serving import (<br>    AiGatewayRateLimit,<br>    AiGatewayRateLimitRenewalPeriod,<br>    AiGatewayRateLimitKey,<br>)<br><br>w = WorkspaceClient()<br><br>w.serving_endpoints.put_ai_gateway(<br>    name=&quot;dataco-summarizer&quot;,<br>    rate_limits=[<br>        # Global service ceiling<br>        AiGatewayRateLimit(<br>            renewal_period=AiGatewayRateLimitRenewalPeriod.MINUTE,<br>            calls=200,<br>            tokens=500_000,<br>            key=AiGatewayRateLimitKey.ENDPOINT,<br>        ),<br>        # Agent SP: generous but bounded<br>        AiGatewayRateLimit(<br>            renewal_period=AiGatewayRateLimitRenewalPeriod.MINUTE,<br>            calls=100,<br>            tokens=200_000,<br>            key=AiGatewayRateLimitKey.SERVICE_PRINCIPAL,<br>            principal=&quot;dataco-summarizer-sp&quot;,<br>        ),<br>        # Human users: conservative<br>        AiGatewayRateLimit(<br>            renewal_period=AiGatewayRateLimitRenewalPeriod.MINUTE,<br>            calls=30,<br>            tokens=50_000,<br>            key=AiGatewayRateLimitKey.USER,<br>        ),<br>    ],<br>)</pre><p>This creates a two-tier system: the agent gets 100 QPM (enough for multi-hop reasoning), while human users get 30 QPM (enough for interactive use). If the agent enters an infinite loop, it hits 100 QPM and gets HTTP 429. The recursion_limit in the application code is the first line of defense; the rate limit is the second.</p><h4>Detecting Agent Loops via System Tables</h4><pre>-- Find service principals with suspiciously high request counts (potential loops)<br>SELECT<br>  requester,<br>  DATE_TRUNC(&#39;hour&#39;, event_time) AS hour,<br>  COUNT(*) AS requests,<br>  SUM(total_tokens) AS tokens,<br>  ROUND(SUM(total_tokens) / COUNT(*), 0) AS avg_tokens_per_call<br>FROM system.ai_gateway.usage<br>WHERE requester_type = &#39;SERVICE_PRINCIPAL&#39;<br>  AND event_time &gt;= current_timestamp() - INTERVAL 24 HOURS<br>GROUP BY requester, hour<br>HAVING requests &gt; 200  -- threshold: more than 200 calls/hour is suspicious<br>ORDER BY requests DESC;</pre><p>If this query returns results, you have an agent that&#39;s either looping or processing an unexpectedly large batch. Investigate the MLflow traces for that service principal to understand why.</p><h4>Key Takeaway</h4><p>For agents, the governance model shifts from “protect the user from the bill” to “protect the bill from the agent.” The combination of application-level max_iterations + gateway-level per-SP rate limits + budget alerts creates a triple safety net that catches loops at whatever layer they manifest.</p><h3>Current Limitations (September 2026)</h3><ul><li><strong>Budget block usage:</strong> Genie budgets only; custom endpoints cannot be blocked automatically.</li><li><strong>External model spend:</strong> not included in standard budget; monitorable via system.ai_gateway.external_model_spend but no automatic enforcement.</li><li><strong>Provisioned throughput:</strong> not tracked by Unity AI Gateway budgets.</li><li><strong>PII detection scope:</strong> US categories only (SSN, US phone format); does not natively cover GDPR European PII.</li><li><strong>Rate limit precision:</strong> burst behavior allowed for low latency (enforcement not instantaneous).</li><li><strong>Guardrails availability:</strong> limited to regions supporting Foundation Model APIs pay-per-token.</li><li><strong>Budget refresh:</strong> alerts based on near real-time; system tables update every few hours.</li><li><strong>Custom policies:</strong> require LLM judge → additional cost per call.</li></ul><h3>When NOT to use Unity AI Gateway</h3><ul><li><strong>Rapid prototyping</strong>: in sandbox/dev, costs are negligible. Overly aggressive rate limits slow down development.</li><li><strong>Massive batch inference</strong>: for scheduled batch jobs, budget is predictable. Provisioned Throughput is better (not tracked by the gateway).</li><li><strong>Single-model internal endpoint</strong>: if one team uses one model, costs are already isolated. The gateway adds complexity without value.</li></ul><p>The gateway delivers maximum ROI when: <strong>many users/agents × many models/endpoints × unpredictable costs</strong>.</p><h3>How It Compares: Unity AI Gateway vs. Alternatives</h3><p>If your stack isn’t 100% Databricks, you may be evaluating external AI gateway solutions. Here’s how they compare:</p><p><strong>Unity AI Gateway</strong> (Databricks)</p><ul><li>Rate limiting: QPM + TPM, per-user/group/service</li><li>Budget control: budgets with block + alerts, UC tag scoping</li><li>Observability: system tables (SQL), built-in dashboard</li><li>Guardrails: safety + PII + custom policies (LLM judge)</li><li>Routing: traffic splitting + fallback across endpoints</li><li>Auth: Unity Catalog (SSO, RBAC, audit logs)</li><li>Data residency: runs in your Databricks workspace (same region)</li><li>Integration: native with Genie, MLflow, Agent Framework, MCP</li><li>Pricing: included in Databricks (no extra gateway fee)</li></ul><p><strong>LiteLLM Proxy</strong> (open source / MLflow AI Gateway)</p><ul><li>Rate limiting: QPM + TPM, per-key</li><li>Budget control: budget alerts (webhook) + reject (HTTP 429)</li><li>Observability: custom DB + Grafana</li><li>Guardrails: via callbacks/plugins</li><li>Routing: load balancing + fallback</li><li>Auth: API key management</li><li>Data residency: self-hosted or cloud</li><li>Integration: SDK wrapper</li><li>Pricing: open source (self-host cost)</li></ul><p><strong>Helicone</strong> (SaaS)</p><ul><li>Rate limiting: per-key, per-org</li><li>Budget control: alerts only</li><li>Observability: web dashboard + API</li><li>Guardrails: content filtering</li><li>Routing: proxy routing</li><li>Auth: API key + org</li><li>Data residency: US/EU cloud</li><li>Integration: SDK wrapper</li><li>Pricing: per-request</li></ul><p><strong>Portkey.ai</strong> (SaaS)</p><ul><li>Rate limiting: per-key, per-org</li><li>Budget control: alerts + soft limits</li><li>Observability: web dashboard + API</li><li>Guardrails: guardrails + fallback</li><li>Routing: gateway routing</li><li>Auth: API key + org</li><li>Data residency: multi-region cloud</li><li>Integration: SDK wrapper</li><li>Pricing: per-request</li></ul><p><strong>When to choose Unity AI Gateway:</strong></p><ul><li>You’re already on Databricks and want zero-friction governance</li><li>You need UC-level RBAC and audit trails (compliance)</li><li>Your agents use Genie, MCP servers, or Mosaic AI endpoints</li><li>You want cost data in the same SQL engine as your analytics</li></ul><p><strong>When to consider alternatives:</strong></p><ul><li>Multi-cloud with no Databricks commitment</li><li>Need gateway features today at GA quality (Unity AI Gateway is Beta)</li><li>Primary models are external (OpenAI, Anthropic) without Databricks endpoints</li><li>Need real-time cost dashboards with sub-minute granularity (system tables lag by hours)</li></ul><h3>Final Thought</h3><p>Let’s return to DataCo’s Monday morning. With Unity AI Gateway in place, here’s what would have happened instead:</p><ol><li>The agent’s retry loop hits 60 QPM per user → <strong>rate limiter kicks in</strong> at request #61. HTTP 429, exponential backoff. The loop self-heals.</li><li>Even if it doesn’t, the $500 shared budget threshold fires at 2 AM → <strong>platform team gets paged</strong>. They kill the endpoint in minutes, not hours.</li><li>Monday morning, the platform lead runs SELECT ... FROM system.ai_gateway.usage WHERE requester = &#39;support-agent-sp&#39; → sees exactly which service principal, which model, how many tokens. Root cause in one query.</li></ol><p>Total damage: $500 instead of $12,400. One Monday ruined vs. one Microsoft Teams notification at 2 AM.</p><p>The tooling isn’t exotic. It’s four configuration steps, five SQL queries, and one architectural principle: <strong>defense in depth</strong>. Rate limits prevent the spike. Budgets cap the damage. Observability explains what happened. Guardrails prevent what shouldn’t happen at all.</p><p>The biggest risk in production AI isn’t a bad model. It’s a good model with no off switch.</p><h3>12. References</h3><ul><li><a href="https://docs.databricks.com/aws/en/ai-gateway/index/">Unity AI Gateway overview</a></li><li><a href="https://docs.databricks.com/aws/en/ai-gateway/rate-limits/">Configure rate limits</a></li><li><a href="https://docs.databricks.com/aws/en/ai-gateway/budgets/">Manage budgets for Unity AI Gateway</a></li><li><a href="https://docs.databricks.com/aws/en/ai-gateway/cost-observability/">Monitor Unity AI Gateway cost</a></li><li><a href="https://docs.databricks.com/aws/en/ai-gateway/configure-ai-gateway-endpoints/">AI Guardrails configuration</a></li><li><a href="https://docs.databricks.com/aws/en/admin/account-settings/budgets/">Create and monitor budgets (Account Console)</a></li><li><a href="https://docs.databricks.com/aws/en/admin/system-tables/billing/">system.billing.usage reference</a></li></ul><blockquote><strong><em>Note:</em></strong><em> This article is based on official Databricks documentation (September 2026). Unity AI Gateway is in Beta; features and limits may change before GA. SQL queries on system tables are tested on workspaces with access enabled to </em><em>system.billing.usage.</em></blockquote><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3baf2083bb00" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-reply-it-datatech/unity-ai-gateway-governing-ai-costs-in-production-3baf2083bb00">Unity AI Gateway: Governing AI Costs in Production</a> was originally published in <a href="https://medium.com/data-reply-it-datatech">Data Reply IT | DataTech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Reinforcement learning for dynamic systems: application of Fitted Q-Iteration methods ​]]></title>
            <link>https://medium.com/data-reply-it-datatech/reinforcement-learning-for-dynamic-systems-application-of-fitted-q-iteration-methods-23cd9fdd770c?source=rss----1431aad73386---4</link>
            <guid isPermaLink="false">https://medium.com/p/23cd9fdd770c</guid>
            <category><![CDATA[reinforcement-learning]]></category>
            <category><![CDATA[fitted-q-iteration]]></category>
            <category><![CDATA[q-learning]]></category>
            <dc:creator><![CDATA[Giorgia Faccanoni]]></dc:creator>
            <pubDate>Wed, 02 Sep 2026 08:01:05 GMT</pubDate>
            <atom:updated>2026-09-02T08:01:05.445Z</atom:updated>
            <content:encoded><![CDATA[<h4><strong><em>Introduction</em></strong></h4><p>Reinforcement Learning (RL) is a machine learning approach designed to solve sequential decision problems in complex environments, where the consequences of actions are not immediately clear. Unlike supervised learning, which requires an explicit target variable, in RL an agent (a software, a robot, or an autonomous vehicle) learns optimal strategies autonomously through direct interaction with the environment, observing feedback in the form of numerical rewards and progressively refining its decisions to maximize cumulative rewards over time.</p><p>Among the different RL algorithms, Q-learning is one of the most well-known and widely used. However, its direct application is limited when dealing with large or continuous state and action spaces, or when direct interaction with the environment is not possible. In these scenarios, Fitted Q-Iteration (FQI) comes into play: an offline approach where the agent learns from a pre-existing dataset and determines the best decision rule (policy) at each time step.</p><p>In this article, FQI is implemented on the House Heating dataset with the goal of finding optimal actions to control the internal temperature of a building over 60 days, balancing comfort with cost minimization.</p><h4><strong><em>Reinforcement Learning</em></strong></h4><p>At each time step <em>t</em>, the agent receives a state <em>St ∈ S</em> and the reward <em>Rt ∈ R</em> from the previous step, and decides the best action <em>At ∈ A</em> to take toward the next state <em>St+1</em>. This process repeats recursively for a predetermined number of iterations or until convergence, with the agent adjusting its actions to maximize rewards both at each step and over the long term.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/461/1*IZVc1kb-Ovl2h0LzHuuQnQ.png" /><figcaption><strong>Reinforcement Learning</strong></figcaption></figure><p>There are several categories of models within reinforcement learning depending on their methodological characteristics.</p><p><strong><em>Offline vs Online Methods</em></strong><br>RL algorithms differ based on how they acquire data. <em>Offline methods</em> operate on pre-existing datasets of finite size, while <em>online methods </em>receive data progressively through continuous agent-environment interaction.</p><p><strong><em>Model-based vs Model-free Methods</em></strong><br><em>Model-based methods</em> define or assume a known model of the environment (transition probabilities and reward function) to simulate future scenarios and plan actions. <em>Model-free methods</em> learn directly from experience without building an explicit model of the environment.</p><p><strong><em>Value-based vs Policy-based Methods</em></strong><br>Model-free methods further divide into <em>value-based models</em>, where the agent estimates the value function <em>V</em>(<em>s</em>) or the <strong>Action-Value function <em>Q</em>(<em>s,a</em>)</strong> (example: Q-learning), and <em>policy-based models</em>, where the agent learns the policy <em>π</em> directly (deterministic or stochastic).</p><p>In all cases, the objective is to estimate a function that determines optimal actions to maximize cumulative rewards. Since future reward values cannot be known in advance, the Value function <em>V</em>(<em>s</em>) is defined to estimate the expected future rewards from a state <em>s </em>under a given policy <em>π</em>. A policy <em>π</em>(<em>a|s</em>) represents the probability of choosing action <em>At = a</em> when in state <em>St = s</em>, and the value function <em>vπ(s</em>) expresses the expected cumulative rewards starting from state <em>s </em>following policy <em>π</em>.</p><p>Another measure of the agent’s behavior quality is the Action-Value function <em>Q</em>(<em>s,a</em>), which represents how good it is to take action <em>a</em> in state <em>s </em>while following policy <em>π.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/645/1*qhulU-HqpqZMXPQJN3yALg.png" /></figure><p>For every policy and state, both functions (Value function and Action-Value function) can be expressed recursively through the Bellman equation, which relates the value of a state to the values of subsequent states weighted by their transition probabilities.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/633/1*IpcqV-ds4FN-_ZG_p_gvNw.png" /><figcaption>Bellman Equation</figcaption></figure><p>Finding an optimal policy means finding <em>π(a|s)</em> that yields high rewards both at each step and over the long term. The optimal Action-Value function <em>Q*</em>(<em>s,a</em>) is the one associated with the optimal policy π*, that is, the best among all possible policies:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/487/1*5quXDHDimka9NMJXmtIIJg.png" /></figure><h4><strong><em>Q-learning and Fitted Q-Iteration</em></strong></h4><p>The key idea of RL is that an agent learns on its own without a preset model dictating correct actions. <em>Temporal-Difference </em>(<em>TD</em>)<em> learning</em> realizes this: the agent learns from experience by continuously comparing predictions with observed results, improving iteratively without predefined instructions. TD algorithms exploit the trade-off between exploration (trying new actions) and exploitation (using known good actions).</p><p>TD methods distinguish between two approaches: <em>on-policy</em>, where the policy used to collect data is the same as the one being optimized, and <strong><em>off-policy</em></strong>, where these two policies are different. In the off-policy context, Q-learning is the fundamental algorithm, from which <strong><em>Fitted Q-Iteration</em></strong> is derived. FQI is an <em>offline, </em><strong><em>batch algorithm</em></strong> that estimates <em>Q</em>(<em>s,a</em>) from a fixed dataset of (<em>s, a, s’, r</em>) tuples, where <em>s</em> is the current state, <em>a</em> is the action taken, <em>s’</em> is the next state, and<em> r</em> is the observed reward, without actively interacting with the environment.</p><p>FQI approximates the Q-function using regression methods (like random forests, generalized additive models, neural networks, or other machine learning models) and derives a control policy as close as possible to the optimal one. When state and action spaces are finite and small, <em>Q</em> can be represented in tabular form. In continuous or very large spaces, FQI uses parametric or non-parametric approximators, solving sequential regression problems at each iteration to iteratively refine the Q-function estimate. This batch approach enables FQI to operate in complex environments while maintaining computational efficiency.</p><p>The FQI algorithm requires four inputs: the discount factor <em>γ</em>, the number of iterations <em>K</em>, a regression model class <em>F</em>, and the dataset (<em>s, a, s’, r</em>) observed, for each time step <em>t = 1,…,Te </em>and episode <em>e = 1,…,E</em>. It initializes <em>Q₀</em> = 0 for every state-action pair, then repeats for <em>K</em> iterations: at each step a new target variable is computed as the sum of the observed reward and the maximized Q-value over optimal actions, and a regression model is fitted by minimizing the squared error.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/705/1*aVLJa5rKajOWTDKX_5Z7XA.png" /></figure><h4><strong><em>Application</em></strong></h4><p>The dataset was generated following a safe-by-design policy, where actions derive from a predefined policy aimed at maximizing system safety. The goal is to use FQI to identify an optimal policy capable of both respecting safety constraints and maximizing rewards. The data represent the control of a building heating system, illustrated below.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/625/1*vn96DhZOdpc0YOFSkcv5iQ.png" /></figure><p>The safe-by-design policy receives the current internal temperature <em>st (state) </em>as input and sets the radiator temperature <em>at (action) </em>accordingly. The action choice is not influenced by the observed reward <em>r</em>. The system is also affected by an uncertain external temperature <em>ξt</em>, which varies throughout the day.</p><p>The safety constraints to be respected are:</p><ul><li>keep the internal temperature as close as possible to 18°C within 30 minutes of turning on the radiator;</li><li>not exceed 19°C in the first 30 minutes;</li><li>maintain internal temperature between 17.5°C and 18.5°C.</li></ul><p>The operational constraint limits actions to a range of 0°C to 70°C.</p><p>The dataset provides observations every 30 minutes over 60 days, recording the internal temperature <em>st</em>, the action <em>at</em> taken according to the safe-by-design policy, and heating costs calculated by multiplying the action by the hourly price. The 60 days correspond to 60 independent episodes, where the state at the start of each day is independent from the previous day’s final state. The objective is to minimize total costs:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/237/1*qAR3dNuYPDTjo6_FEOEDZQ.png" /></figure><p>The reward to be maximized is defined as the sum of two components: the negative heating cost, which directly incentivizes cost reduction, and a penalty term weighted by λ that prevents the algorithm from selecting actions that would compromise the internal temperature comfort. The penalty weight <em>λ</em> was set to the optimal value of 600.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/235/1*sn2bWnQ5SVIzEU2b76EutQ.png" /></figure><p>Since FQI requires a regression model to estimate the target values at each iteration, several regression models were trained on the training set (40 days) and evaluated on the test set (20 days): linear regression, additive model with thin plate spline regression, random forest, and neural networks. Each model was evaluated by comparing the cost reduction achieved under the new policy against those incurred by the safe-by-design policy.</p><p>The table below presents the results, showing temperature quantiles achieved under the new policy and the number of constraint violations outside [17.5°; 18.5°] with each regression model.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/728/1*_0S69V3zyfO9WkZ8ol8QNg.png" /></figure><p>The graph below shows the internal temperature trend over the 20 test days for the <strong><em>thin plate spline model</em></strong>. The downward peaks correspond to the start of each day. From the second state onward, the model brings temperature within safety constraints and maintains it relatively stable throughout the day. Only one violation above 18.5°C is observed on the second test day. This model shows greater temperature variability compared to others, which enables lower operating costs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/641/1*8Quoer7ZYO4kpDi_r1r1xg.png" /></figure><p>In evaluating the models, besides analyzing state values, the costs generated by each model’s actions were calculated across the 20 test days. The table shows the difference in total costs between the new policy and the safe-by-design policy for each regression model.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/456/1*TGk-nYmD1l5_R1jTvX6L_A.png" /></figure><p>The model that best balances constraint satisfaction and cost reduction is the <strong><em>additive model with thin plate spline regression</em></strong>, which delivers lower costs compared to all other models.</p><p>This model was applied to the full 60-day dataset, achieving very satisfactory temperature levels with no further anomalies and a total cost reduction of −€4,547.75 compared to the safe-by-design policy.</p><h4><strong><em>Conclusion</em></strong></h4><p>This work applied the Fitted Q-Iteration algorithm with several regression models to estimate the Action-Value function on the House Heating dataset. Among the models tested, the generalized additive model with thin plate spline achieved the best performance, capturing non-linear relationships between variables while respecting temperature constraints and reducing operational costs. FQI proves to be as a robust approach for complex control problems, where choosing the right regression model is key to achieving reliable and efficient solutions.</p><p><strong><em>References</em></strong></p><p>Sutton, R. S., &amp; Barto, A. G. (2018). <em>Reinforcement Learning: An Introduction</em>. MIT Press.</p><p>Ernst, D., Geurts, P., &amp; Wehenkel, L. (2005). Tree-based batch mode reinforcement learning. <em>Journal of Machine Learning Research</em>, 6, 503–556.</p><p>Puterman, M. L. (2014). <em>Markov Decision Processes: Discrete Stochastic Dynamic Programming</em>. John Wiley &amp; Sons.</p><p>Hastie, T., Tibshirani, R., &amp; Friedman, J. (2009). <em>The Elements of Statistical Learning</em>. Springer.</p><p>Agarwal, A., Jiang, N., Kakade, S. M., &amp; Sun, W. (2019). Reinforcement learning: Theory and algorithms. University of Washington.</p><p>Munos, R. (2005). Error bounds for approximate value iteration. <em>Proceedings of AAAI</em>, 2.</p><p>James, G., Witten, D., Hastie, T., &amp; Tibshirani, R. (2013). <em>An Introduction to Statistical Learning</em>. Springer.</p><p>Columbia University Mailman School of Public Health (2011). Thin plate spline regression.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=23cd9fdd770c" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-reply-it-datatech/reinforcement-learning-for-dynamic-systems-application-of-fitted-q-iteration-methods-23cd9fdd770c">Reinforcement learning for dynamic systems: application of Fitted Q-Iteration methods ​</a> was originally published in <a href="https://medium.com/data-reply-it-datatech">Data Reply IT | DataTech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop Mixing Infrastructure, Database State, and Identity in AWS Aurora]]></title>
            <link>https://medium.com/data-reply-it-datatech/stop-mixing-infrastructure-database-state-and-identity-in-aws-aurora-e686359beecd?source=rss----1431aad73386---4</link>
            <guid isPermaLink="false">https://medium.com/p/e686359beecd</guid>
            <category><![CDATA[token]]></category>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[terraform]]></category>
            <category><![CDATA[devsecops]]></category>
            <dc:creator><![CDATA[Dario Ostinelli]]></dc:creator>
            <pubDate>Wed, 19 Aug 2026 07:16:01 GMT</pubDate>
            <atom:updated>2026-08-19T07:16:01.250Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R8Ifyrhmg8KDlJDr-ST2mA.png" /></figure><p>Database credentials in CI/CD pipelines are still surprisingly common, even in mature AWS setups. In our case, the issue was not just <em>where</em> the credentials lived, but the fact that the same system, GitLab CI, was being used to orchestrate infrastructure, manage database state, and distribute identity information. <br>This led to an unnecessary coupling between Terraform, database bootstrap logic, and secret management, making the system harder to reason about, harder to secure, and harder to evolve.</p><p>The solution was not a better way to pass secrets, but a clearer separation of responsibilities across three domains: i<strong>nfrastructure provisioning, database state, and identity management in AWS Aurora</strong>.</p><p>In this article, we will intentionally <strong>separate the conceptual model from the practical implementation</strong>. <br>In the first section, we will focus on the architectural principles behind the <strong>separation of infrastructure, database state, and identity</strong> in AWS Aurora. In the second section, we will move to the <strong>actual configuration</strong>, showing how these ideas are translated into Terraform, SQL-based bootstrap scripts, and the use of AWS IAM and Secrets Manager in a real-world setup.</p><h3>Separate Infrastructure and DB State</h3><blockquote>“Infrastructure as Code does not mean embedding your database logic into Terraform.”</blockquote><p>Infrastructure as Code is responsible for creating the database cluster, while database configuration remains expressed in standard SQL. This separation provides several key benefits:</p><ul><li><strong>Portability:</strong> the same database can be recreated and configured consistently across different cloud providers. Migration costs are significantly reduced when all setup logic (DDL, roles, triggers, and permissions ) is centralized, standardized, and version-controlled.</li><li><strong>Reduced coupling:</strong> decoupling Terraform from database-specific logic avoids tight integration with a specific engine and reduces reliance on provider-specific abstractions.</li><li><strong>Clear lifecycle separation:</strong> infrastructure tends to evolve slowly, while database state (schemas, roles, grants, and migrations) changes more frequently. Keeping them separate reflects their natural change cadence and reduces friction in both domains.</li><li><strong>Faster onboarding:</strong> SQL is a universal language in the data ecosystem. Keeping database configuration in SQL improves readability and allows engineers to understand and contribute quickly.</li></ul><p>In summary, treating infrastructure provisioning and database state as separate concerns leads to a significantly <strong>cleaner and more maintainable system design</strong>. Terraform should focus on provisioning reliable and repeatable infrastructure, while SQL remains the source of truth for how data structures, roles, and permissions are defined and evolved.</p><h3>Separate Infrastructure and Identity</h3><blockquote>“CI/CD pipelines should orchestrate infrastructure, not become long-lived secret vaults.”</blockquote><p>Managing production database credentials directly through CI/CD variables introduces an unnecessary and often overlooked risk surface. While it may appear convenient, it effectively <strong>turns the pipeline into a persistent distribution point for sensitive identity information</strong>, increasing blast radius and complicating access control, rotation, and auditability.</p><p>We aim to achieve a model where <strong>CI/CD systems only assume temporary permissions</strong> to orchestrate infrastructure changes, without ever becoming a storage layer for sensitive database credentials.</p><p>To do so, on one side, the CI/CD pipeline is granted only ephemeral credentials, typically by <strong>assuming an IAM role at runtime</strong>. In some setups, even when the runner itself operates with elevated permissions, access is still mediated through temporary credentials and tightly controlled roles, <strong>with secrets retrieved dynamically from a managed secret store and continuously rotated</strong>.</p><p>On the other side, database access for applications is handled through <strong>dedicated technical users</strong> with least-privilege permissions. By leveraging IAM authentication mechanisms, these <strong>users can be defined without static passwords</strong>, meaning they cannot be accessed through traditional credential-based authentication at all. Access is enforced exclusively via IAM-issued authentication tokens, ensuring that <strong>neither the CI/CD pipeline nor application services rely on long-lived database credentials</strong>.</p><p>This approach eliminates the need to manage persistent superuser credentials in CI/CD, while also removing static credentials for all application-level database access, significantly reducing overall credential sprawl and operational risk.</p><h3>Practical implementation</h3><h4>Repository structure</h4><p>A clean separation of concerns starts at the repository level. The infrastructure, database bootstrap logic, and CI pipelines are explicitly isolated to reflect their different responsibilities:</p><pre>terraform/<br>  └── main.tf<br>  └── output.tf<br>  └── variables.tf<br><br>sql/<br>  └── setup/<br>      ├── 00_create_schema.sql<br>      ├── 01_create_extensions.sql<br>      ├── 02_ddl.sql<br>      └── 03_grant.sql<br><br>pipelines/<br>  ├── infra.yml<br>  └── db_setup.yml<br><br>.gitlab-ci.yml</pre><p>This structure makes the system intent explicit:</p><ul><li><strong>Terraform </strong>is responsible for provisioning infrastructure components such as the Aurora cluster and networking configuration.</li><li><strong>SQL scripts</strong> define the database state in a portable, engine-agnostic way.</li><li><strong>CI pipelines</strong> orchestrate execution without embedding domain logic.</li></ul><h4>Gitlab CI Pipeline</h4><p>The CI/CD flow is intentionally minimal and divided into two distinct stages, which will be illustrated in the accompanying diagram:</p><figure><img alt="Minimal GitLab CI pipeline showing two stages: Infrastructure (Terraform plan and apply) and Database setup (idempotent SQL bootstrap execution)." src="https://cdn-images-1.medium.com/max/566/1*amdG_eaTE6PajmrTksJSYw.png" /><figcaption>Minimal GitLab CI pipeline showing two stages: Infrastructure (Terraform plan and apply) and Database setup (idempotent SQL bootstrap execution).</figcaption></figure><p><strong>Infrastructure stage</strong></p><p>This example is kept simple, but in a real production environment you would need to consider that the pipeline should be extended with a <strong>proper remote state backend and locking mechanism,</strong> together with <strong>IAM-based authentication and managed secret retrieval</strong>.</p><pre>dev-plan:<br>  stage: infra<br>  script:<br>    - cd terraform/infra<br>    - terraform init<br>    - terraform plan -out=tfplan<br>  artifacts:<br>    paths:<br>      - terraform/infra/tfplan<br><br><br>dev-apply:<br>  stage: infra<br>  script:<br>    - cd terraform/infra<br>    - terraform init<br>    - terraform apply -auto-approve tfplan<br>    - terraform output -json &gt; terraform_outputs.json<br>  artifacts:<br>    paths:<br>      - terraform/infra/terraform_outputs.json<br>  needs:<br>    - dev-plan<br>  when: manual</pre><p><strong>Database setup stage</strong></p><p>This stage performs the database bootstrap after infrastructure provisioning is completed. It retrieves all required connection parameters from Terraform outputs, fetches the database credentials from AWS Secrets Manager, and executes the SQL setup scripts in a deterministic order to initialize and configure the database state.</p><pre>db_setup_dev:<br>  stage: db-setup<br>  needs:<br>    - job: dev-apply<br>      artifacts: true<br>  script:<br>    - echo &quot;Starting database setup&quot;<br><br>    - export PGHOST=$(jq -r &#39;.aurora.value.endpoint&#39; terraform/infra/terraform_outputs.json)<br>    - export PGDATABASE=$(jq -r &#39;.aurora.value.database_name&#39; terraform/infra/terraform_outputs.json)<br>    - export DB_MASTER_USER_SECRET_ARN=$(jq -r &#39;.aurora.value.master_user_secret_arn&#39; terraform/infra/terraform_outputs.json)<br><br>    - echo &quot;Retrieving credentials from AWS Secrets Manager&quot;<br>    - SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id &quot;$DB_MASTER_USER_SECRET_ARN&quot; --query SecretString --output text)<br>    - export PGUSER=$(echo $SECRET_JSON | jq -r &#39;.username&#39;) <br>    - export PGPASSWORD=$(echo $SECRET_JSON | jq -r &#39;.password&#39;)<br>    - export PGPORT=5432<br><br>    - echo &quot;Executing SQL bootstrap scripts&quot;<br>    - |<br>      for script in $(find sql/setup -type f -name &quot;*.sql&quot; | sort); do<br>        echo &quot;Executing: $script&quot;<br>        psql -v ON_ERROR_STOP=1 -f &quot;$script&quot;<br>      done</pre><p><strong>NOTE: </strong>Each SQL script is designed to be safely re-runnable, ensuring that the pipeline remains stateless and resilient to partial failures or re-executions.</p><p>The CI pipeline does not define database configuration. It only consumes the outputs of the infrastructure layer.</p><h4>Terraform configuration</h4><p>This module provisions the core Aurora PostgreSQL cluster and its supporting infrastructure with a strict focus on infrastructure-level concerns only.</p><pre>resource &quot;aws_db_subnet_group&quot; &quot;this&quot; {<br>  name       = var.cluster_identifier<br>  subnet_ids = var.subnet_ids<br>}<br><br>resource &quot;aws_rds_cluster_parameter_group&quot; &quot;this&quot; {<br>  name        = var.cluster_identifier<br>  family      = &quot;aurora-postgresql${var.engine_version}&quot;<br>  description = &quot;Cluster parameter group for ${var.cluster_identifier}&quot;<br>}<br><br>resource &quot;aws_rds_cluster&quot; &quot;this&quot; {<br>  cluster_identifier              = var.cluster_identifier<br>  engine                          = &quot;aurora-postgresql&quot;<br>  engine_version                  = var.engine_version<br>  database_name                   = var.database_name<br>  master_username                 = var.master_username<br>  manage_master_user_password     = true<br>  iam_database_authentication_enabled = true<br>  storage_encrypted              = true<br>  vpc_security_group_ids         = var.security_group_ids<br>  db_subnet_group_name           = aws_db_subnet_group.this.name<br>  db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.this.name<br>}<br><br>resource &quot;aws_rds_cluster_instance&quot; &quot;this&quot; {<br>  identifier         = &quot;${var.cluster_identifier}-instance&quot;<br>  cluster_identifier = aws_rds_cluster.this.id<br>  instance_class     = var.instance_class<br>}</pre><p>A master username is still required by Aurora; however, by enabling manage_master_user_password, the actual master password is fully managed and rotated by AWS rather than being defined, stored, or handled by the CI/CD pipeline.</p><p>This design ensures that <strong>Terraform remains responsible for provisioning the database infrastructure,</strong> while credential lifecycle management is delegated entirely to AWS services, reducing secret exposure and eliminating long-lived administrative credentials in the pipeline.<br>To make the different components work together, Terraform is used as the single source of truth for all runtime configuration required by the CI pipeline, exposing only the necessary outputs for connectivity and secret resolution.<strong> The CI/CD layer then consumes these outputs</strong> to orchestrate the database bootstrap process without introducing any hardcoded infrastructure or credential dependencies.</p><pre>output &quot;aurora&quot; {<br>  description = &quot;Aurora cluster connection and bootstrap metadata&quot;<br>  value = {<br>    endpoint              = aws_rds_cluster.this.endpoint<br>    database_name         = aws_rds_cluster.this.database_name<br>    master_user_secret_arn = aws_rds_cluster.this.master_user_secret[0].secret_arn<br>  }<br>}</pre><p>To make the example work, the following variable definition can be used. Please remember to check if this configuration suits your architectural needs before applying it.</p><pre>variable &quot;cluster_identifier&quot; {<br>  description = &quot;Identifier for the Aurora cluster&quot;<br>  type        = string<br>  default     = &quot;app-aurora-cluster&quot;<br>}<br><br>variable &quot;engine_version&quot; {<br>  description = &quot;Aurora PostgreSQL engine version&quot;<br>  type        = string<br>  default     = &quot;17&quot;<br>}<br><br>variable &quot;instance_class&quot; {<br>  description = &quot;Instance class for Aurora cluster instances&quot;<br>  type        = string<br>  default     = &quot;db.t4g.medium&quot;<br>}<br><br>variable &quot;database_name&quot; {<br>  description = &quot;Initial database name&quot;<br>  type        = string<br>  default     = &quot;appdb&quot;<br>}<br><br>variable &quot;master_username&quot; {<br>  description = &quot;Master username for the Aurora cluster&quot;<br>  type        = string<br>  default     = &quot;admin&quot;<br>}<br><br><br>variable &quot;subnet_ids&quot; {<br>  description = &quot;Subnets for the DB subnet group&quot;<br>  type        = list(string)<br>  default     = []<br>}<br><br>variable &quot;security_group_ids&quot; {<br>  description = &quot;Security groups attached to the cluster&quot;<br>  type        = list(string)<br>  default     = []<br>}</pre><h4>SQL configuration</h4><p>In this section we introduce the actual SQL bootstrap scripts that define the database state. The focus here is on simplicity and repeatability: <strong>each script is designed to be idempotent</strong>, meaning it can be safely executed multiple times without side effects, ensuring predictable behaviour across deployments.</p><pre>-- 00_create_schema.sql<br>CREATE SCHEMA IF NOT EXISTS app;<br><br>-- 01_create_extensions.sql<br>CREATE EXTENSION IF NOT EXISTS ltree;<br><br>-- 02_ddl.sql<br>CREATE TABLE IF NOT EXISTS app.users (<br>    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),<br>    email TEXT NOT NULL UNIQUE,<br>    created_at TIMESTAMP DEFAULT now()<br>);<br><br>-- 03_grant.sql<br>---- Create a technical role (if not exists)<br>DO $$<br>BEGIN<br>    IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = &#39;app_user&#39;) THEN<br>        CREATE USER app_user;<br>    END IF;<br>END<br>$$;<br><br>---- Grant permissions on schema and tables<br>GRANT USAGE ON SCHEMA app TO app_user;<br>GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_user;<br><br>---- Ensure future tables are also covered<br>ALTER DEFAULT PRIVILEGES IN SCHEMA app<br>GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;<br><br>---- Integration with IAM<br>GRANT rds_iam TO app_user;</pre><h4>IAM Authentication</h4><p>Now, the database is fully defined and provisioned inside a centralized infrastructure repository that owns both the provisioning layer and the database state lifecycle.</p><p>On top of this, <strong>any application that needs to access the database does not receive credentials directly</strong>. Instead, it is granted access by attaching an explicit AWS IAM policy to its execution role, allowing it to generate short-lived authentication tokens for the corresponding database identity (e.g. app_user).</p><pre>data &quot;aws_iam_policy_document&quot; &quot;aurora_iam_auth&quot; {<br>  statement {<br>    effect = &quot;Allow&quot;<br><br>    actions = [<br>      &quot;rds-db:connect&quot;<br>    ]<br><br>    resources = [<br>      &quot;arn:aws:rds-db:${var.region}:${var.account_id}:dbuser:${aws_rds_cluster.this.cluster_resource_id}/app_user&quot;<br>    ]<br>  }</pre><p>This effectively extends the identity boundary from the database layer to the cloud identity layer. <strong>Access is no longer managed through shared secrets or connection strings, but through clearly defined IAM roles and policies that are fully auditable and scoped to a single purpose</strong>. Each application becomes responsible for its own identity, while the database remains agnostic to how that identity is provisioned outside of the rds_iam mapping.</p><p>The result is a model where infrastructure, database state, and identity are cleanly separated: the repository owns the system definition, Terraform provisions the infrastructure, SQL defines the persistent state, and IAM governs access.</p><h3>Trade-offs and Known Limitations</h3><p>Every architecture carries costs. This model <strong>introduces some complexity</strong> that may or may not be justified depending on your system’s scale, team structure, and security requirements. Understanding these <strong>trade-offs</strong> before committing helps you decide whether the benefits outweigh the overhead.</p><ul><li><strong>Connection rate limit</strong> : IAM authentication on Aurora supports approximately <strong>256 new connections per second per cluster.</strong> For sustained workloads behind a connection pool this is rarely a constraint, but some architectural patterns can reach this ceiling;</li><li><strong>Token refresh: </strong>IAM auth tokens expire after 15 minutes, so your application or connection <strong>driver must handle renewal</strong>. Some drivers support transparent refresh, but others require explicit retry and renewal logic in your connection layer;</li><li><strong>Idempotent SQL discipline: </strong>Not all DDL is safely re-runnable. An ALTER TABLE that adds a column succeeds once; the second run fails unless wrapped in conditional logic. Data migrations require similar care. Establish conventions and guards (IF NOT EXISTS, version checks) to keep your bootstrap reliably repeatable.</li></ul><h3>Wrap-up and Key Takeaways</h3><p>Separating infrastructure provisioning from database state management adds a moving part, but in practice it reflects the reality that these two lifecycles move at different speeds, often owned by different teams, and governed by different change-control rules. <br>Treating them as one monolithic concern tends to create friction once an organization grows past a single squad.</p><p><strong>IAM authentication</strong> removes long-lived credentials from the picture, replacing shared secrets with short-lived, auditable tokens that prove identity without ever touching a password vault. <br><strong>CI/CD pipelines</strong> then act as orchestrators, coordinating provisioning and deployment without storing or transmitting secrets that could leak.</p><p>Together these choices give you a system where every action is attributable, every permission is scoped, and <strong>every layer can evolve independently.</strong> In our experience, starting with this foundation early is far easier than retrofitting security into a system that wasn’t designed for it.</p><h3>What’s Next</h3><p>The implementation shown is intentionally scoped to illustrate the core separation model. A <strong>production-grade setup</strong> would naturally extend it in several directions:</p><ul><li><strong>Schema migration tooling: </strong>Adding a dedicated CI stage with Flyway or Liquibase for incremental schema evolution. It plugs naturally into the same architecture: same Terraform outputs, same Secrets Manager retrieval, no new static credentials. The bootstrap establishes the initial state; the migration tool manages evolution over time.</li><li><strong>Remote state and locking: </strong>The Terraform setup shown uses local state for simplicity. In production, a remote backend is essential to avoid drift and enable team collaboration.</li><li><strong>Multi-environment promotion: </strong>The pipeline shown targets a single environment. Extending it to dev → staging → prod with environment-specific variable files and approval gates is a natural next step.</li><li><strong>Connection pooling with IAM auth: </strong>IAM authentication tokens expire after 15 minutes. For high-throughput applications, integrating RDS proxy or a similar connection pooler becomes critical to avoid constant re-authentication overhead and connection churn.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e686359beecd" width="1" height="1" alt=""><hr><p><a href="https://medium.com/data-reply-it-datatech/stop-mixing-infrastructure-database-state-and-identity-in-aws-aurora-e686359beecd">Stop Mixing Infrastructure, Database State, and Identity in AWS Aurora</a> was originally published in <a href="https://medium.com/data-reply-it-datatech">Data Reply IT | DataTech</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>