<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: BigCircle</title>
    <description>The latest articles on DEV Community by BigCircle (bigcircle).</description>
    <link>https://dev.to/bigcircle</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15487%2F363e60e1-6735-42a7-803f-247ccfce816b.png</url>
      <title>DEV Community: BigCircle</title>
      <link>https://dev.to/bigcircle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bigcircle"/>
    <language>en</language>
    <item>
      <title>RAG vs fine-tuning vs agentic search, and when to use each</title>
      <dc:creator>Tirth Gajjar</dc:creator>
      <pubDate>Sat, 31 Oct 2026 18:40:36 +0000</pubDate>
      <link>https://dev.to/bigcircle/rag-vs-fine-tuning-vs-agentic-search-and-when-to-use-each-4fll</link>
      <guid>https://dev.to/bigcircle/rag-vs-fine-tuning-vs-agentic-search-and-when-to-use-each-4fll</guid>
      <description>&lt;p&gt;Choose between RAG and fine-tuning by the failure you can see. Use retrieval when the model is missing a fact, and fine-tuning when the facts are right and a specialist says the output reads wrong. Use agentic search, also called agentic RAG, only when no single query can find the answer.&lt;/p&gt;

&lt;p&gt;Prompting comes before all three, because it costs nothing extra to try. Every extra search step is another model call the user waits for, and most production systems end up running several of these layers at once.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Prompting&lt;/th&gt;
&lt;th&gt;Retrieval&lt;/th&gt;
&lt;th&gt;Fine-tuning&lt;/th&gt;
&lt;th&gt;Agentic search&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Failure you can observe&lt;/td&gt;
&lt;td&gt;Output is inconsistent, or wrong in a way you cannot describe&lt;/td&gt;
&lt;td&gt;Model does not know a fact, or knows an old one&lt;/td&gt;
&lt;td&gt;Facts are right, form is wrong to a specialist&lt;/td&gt;
&lt;td&gt;No single query can retrieve the answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it changes&lt;/td&gt;
&lt;td&gt;Instructions, examples, and the output schema&lt;/td&gt;
&lt;td&gt;Which passages are fetched for this question&lt;/td&gt;
&lt;td&gt;Default form, and judgements a written rule does not settle&lt;/td&gt;
&lt;td&gt;Reach, by searching again from what the last search returned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it leaves alone&lt;/td&gt;
&lt;td&gt;The weights, and which documents exist&lt;/td&gt;
&lt;td&gt;How the model writes once the right passage is present&lt;/td&gt;
&lt;td&gt;A fact that changed after the training run&lt;/td&gt;
&lt;td&gt;The accuracy of a question one search could have answered&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What you build&lt;/td&gt;
&lt;td&gt;A prompt, examples and an output schema&lt;/td&gt;
&lt;td&gt;An index, the jobs that keep it current, and permission filters&lt;/td&gt;
&lt;td&gt;A training set, an eval against the base model, and a hosted model&lt;/td&gt;
&lt;td&gt;A loop with a stop condition and a record of every step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Added latency&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;One search before the model call&lt;/td&gt;
&lt;td&gt;None once served&lt;/td&gt;
&lt;td&gt;One model call per step, each waiting on the last&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per call&lt;/td&gt;
&lt;td&gt;Tokens&lt;/td&gt;
&lt;td&gt;Tokens, the search, and the passages added to the prompt&lt;/td&gt;
&lt;td&gt;Can fall, if a small tuned model replaces a large one&lt;/td&gt;
&lt;td&gt;Multiplied by the number of steps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ongoing burden&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Keeping the index current&lt;/td&gt;
&lt;td&gt;A model to own and retrain&lt;/td&gt;
&lt;td&gt;Showing the loop stopped for a good reason&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How Bigcircle handles this&lt;/td&gt;
&lt;td&gt;Schema-constrained decoding sits in the same request as the other layers on the trademark platform&lt;/td&gt;
&lt;td&gt;Retrieval restricted to a curated index of authorities on that platform, live since 2023&lt;/td&gt;
&lt;td&gt;A fine-tuned model in the same request, for structure and register, which retrieval could not fix&lt;/td&gt;
&lt;td&gt;On Prospex, a search loop only after hybrid retrieval stopped improving. The insurance voice agent loads its context when the call opens and does not search during a turn&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The standard answer to this question is "RAG for knowledge, fine-tuning for behaviour." That answer is close, and it breaks on the first system that needs both. Our &lt;a href="https://bigcircle.ai/work/trademark-drafting-fine-tuned-llm?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rag-vs-fine-tuning-vs-agentic-search" rel="noopener noreferrer"&gt;trademark drafting platform&lt;/a&gt; has been live since 2023 with a fine-tuned model, retrieval over a curated index of authorities, and schema-constrained decoding in the same request. We did not run all three because we could not choose. Each one fixes a failure the other two cannot reach.&lt;/p&gt;

&lt;p&gt;So start from the failure you are looking at, and then pick the least expensive layer that fixes it. Read the columns of the table above from left to right. Prompting is the column to exhaust first, and a later column does not fix a failure that belongs in an earlier one.&lt;/p&gt;

&lt;h2&gt;
  
  
  When is it just a prompting problem?
&lt;/h2&gt;

&lt;p&gt;When the task is underspecified rather than unknown to the model. Every team believes it has finished prompting, so test it. Take your prompt, hand it to a contractor who knows nothing about your product, and ask them to do the task.&lt;/p&gt;

&lt;p&gt;If they come back with questions, the model has those questions too, and it does not ask them. It picks an interpretation, commits to it, and you file the result as a model failure. A support summariser gets a ticket with three separate issues in it, and nothing in the prompt decides whether that is one summary or three. The model picks one reading, and next Tuesday it picks the other. No amount of retrieval touches that, because nothing is missing from what the model knows.&lt;/p&gt;

&lt;p&gt;A fix at this layer takes an afternoon and leaves nothing new to host or retrain, which is why it comes first. Before you spend money on another layer, do the following.&lt;/p&gt;

&lt;p&gt;Constrain the output mechanically. Structured output with constrained decoding removes invalid tokens from the choices the model has while it writes, so the output always parses. If you are still parsing free text with a regex and a retry loop, the defect is in the prompt, and it will get filed against the model.&lt;/p&gt;

&lt;p&gt;Show the output instead of describing it. Four real examples of the exact output do more than two paragraphs of adjectives, because the model copies what it is shown (in-context learning). Before anyone proposes a fine-tune, count the examples in the prompt. If the count is zero, the free version of the same idea has not been tried yet.&lt;/p&gt;

&lt;p&gt;Write the refusal condition, one sentence along the lines of "if the retrieved passages do not contain the answer, say so and stop". Without it the model fills a gap with something plausible, and a reader cannot tell which sentences came from the passages and which were a hallucination.&lt;/p&gt;

&lt;h3&gt;
  
  
  You cannot skip the eval set
&lt;/h3&gt;

&lt;p&gt;Collect twenty to fifty real cases with known-good outputs and score them the same way on every run. Until that set exists you cannot tell a fix from a lucky sample, and you cannot tell whether the next layer helped. Evals are how you read every decision below this point. &lt;a href="https://bigcircle.ai/guides/how-to-build-llm-evals?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rag-vs-fine-tuning-vs-agentic-search" rel="noopener noreferrer"&gt;How to build LLM evals&lt;/a&gt; covers building the set, from the first failure note through a model judge.&lt;/p&gt;

&lt;h2&gt;
  
  
  When do you need RAG?
&lt;/h2&gt;

&lt;p&gt;Use retrieval when the failure is missing or stale knowledge: facts specific to your business, documents written after the model's knowledge cutoff, anything that changes weekly, and anything a user is allowed to see only part of.&lt;/p&gt;

&lt;p&gt;Your refund window changed in March and the model still answers with the old one. Fine-tuning will not fix that, and it will not fix it again in June when the window changes back. The mechanics are in &lt;a href="https://bigcircle.ai/guides/why-rag-systems-fail-in-production?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rag-vs-fine-tuning-vs-agentic-search" rel="noopener noreferrer"&gt;why RAG systems fail in production&lt;/a&gt;. The question here is whether retrieval alone is enough.&lt;/p&gt;

&lt;p&gt;Retrieval quality depends less on the model or the vector database than on four choices: chunking, embeddings, hybrid search so that part numbers and names still match exactly, and reranking so the best passage reaches the context window rather than a related one.&lt;/p&gt;

&lt;p&gt;Work on those four before you touch the model, because no model can use a passage that never reached it. In Anthropic's &lt;a href="https://www.anthropic.com/engineering/contextual-retrieval" rel="noopener noreferrer"&gt;contextual retrieval&lt;/a&gt; tests from September 2024, the share of queries whose right chunk missed the top 20 fell from 5.7% to 1.9% once each chunk carried 50 to 100 tokens of generated context, keyword search ran beside embeddings, and a reranker sorted the result. The added context alone cut failures by 35%, keyword search took the cut to 49%, and the reranker to 67%. The model stayed the same throughout, so the whole gain came from those choices.&lt;/p&gt;

&lt;p&gt;On &lt;a href="https://bigcircle.ai/work/prospect-intelligence-retrieval?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rag-vs-fine-tuning-vs-agentic-search" rel="noopener noreferrer"&gt;Prospex&lt;/a&gt;, our own prospect research engine, semantic and keyword search run together, and a claim with no retrieved passage behind it does not appear. On one run that took 420 collected items down to the 6 that answered the question. We estimate it makes 50 to 60 percent fewer external API calls on repeated runs, and that saving comes from caching rather than from retrieval: the runtime is cache-first, so a rerun on an overlapping account reuses work instead of paying for it again.&lt;/p&gt;

&lt;p&gt;Teams get two things wrong at this layer. Citations are an architecture decision, and teams keep treating them as formatting. If a regulated user has to trust a number, they need to open the page it came from.&lt;/p&gt;

&lt;p&gt;On our ESG filings platform every extracted field carries a link to its source page and a confidence score, and a low-confidence field goes to review instead of into a dashboard. We measured the analysis cycle falling from weeks to hours once the pipeline replaced manual extraction and spreadsheet work. Most of the recovered time came from the confidence flags: analysts who could see which figures were uncertain stopped re-deriving earlier work to be safe.&lt;/p&gt;

&lt;p&gt;Permissions belong in the query. A filter applied after retrieval runs once the document is already in the search results, and anything downstream that logs, caches or summarises those results can leak it. Filter by the user's access inside the search itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three ways retrieval turns out not to be enough
&lt;/h3&gt;

&lt;p&gt;Each row below is a symptom you can see in a trace, and the column on the right names where it sends you.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you see&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;th&gt;Where to go&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Right passage in context, answer still reads wrong to a specialist&lt;/td&gt;
&lt;td&gt;Form problem, not fact problem&lt;/td&gt;
&lt;td&gt;Fine-tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Right passage cannot be found by any single query&lt;/td&gt;
&lt;td&gt;Question has to be split into several searches&lt;/td&gt;
&lt;td&gt;Agentic search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The corpus does not contain the answer&lt;/td&gt;
&lt;td&gt;Not a technique problem&lt;/td&gt;
&lt;td&gt;Say so and stop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That third row is worth saying out loud early, because no layer fixes a missing document.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you fine-tune instead of using RAG?
&lt;/h2&gt;

&lt;p&gt;When the failure is form, or judgement a written rule does not settle, and never when it is missing knowledge. Fine-tuning does not teach facts reliably. It can be made to, badly, and then you cannot update a fact without retraining. What it moves is default behaviour: output shape, register, domain convention, and the small judgements a specialist makes without thinking.&lt;/p&gt;

&lt;p&gt;The usual test is whether the expert can write the rule down. That is close, and the sharper test is whether the written rule decides the output.&lt;/p&gt;

&lt;p&gt;Trademark work is the clearest case we have, and the problem is not that practitioners cannot explain anything. They can. The rules are written down, and they are general, and a general rule does not settle a specific draft on its own. Precedent settles it: how comparable cases were decided, and how those decisions have shifted what a defensible specification looks like in practice.&lt;/p&gt;

&lt;p&gt;You cannot compress that into prompt instructions, because it is not one book. It is applied interpretation spread across many decisions, where the same rule gives different results depending on the case in front of you. So the layers split cleanly, and each one takes the part it can hold:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Where it lives&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The general rules, written and stable&lt;/td&gt;
&lt;td&gt;Prompt instructions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The specific authorities and prior marks for this case&lt;/td&gt;
&lt;td&gt;Retrieval, restricted to approved sources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The pattern of how those rules get applied in practice&lt;/td&gt;
&lt;td&gt;The fine-tune&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;People try to solve that third row with a longer prompt. A prompt listing the general rules produces a draft that is technically compliant and reads wrong to anyone who has practised. Before training, we normalised years of attorney opinions into one five-section template, tagged by mark type, jurisdiction and NICE class. Without that step the model would have learned a dozen individual writing styles and averaged them. It mattered more than we expected.&lt;/p&gt;

&lt;p&gt;The fine-tune also has a failure we designed around. It improves how a citation is phrased, which is the wrong thing to improve when the authority might not exist. The fine-tuned model is never asked to remember which classes exist, which prior marks conflict, or what the registry shows this week. Retrieval over the curated index supplies those, a validator rejects any citation that does not resolve, and an attorney reviews and signs the draft.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it costs beyond the training run
&lt;/h3&gt;

&lt;p&gt;Budget the training run last. It is the smallest line and often the only one in the estimate, because LoRA and QLoRA shrank the compute. The other lines did not shrink:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;What it looks like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;Examples of the right output, written or checked by an expert. On the trademark platform that meant normalising years of opinions into one template before any training&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evaluation&lt;/td&gt;
&lt;td&gt;Proof the tuned model is better rather than different. The trademark platform logs the base and fine-tuned models on the same inputs, so a regression shows up as a score that moved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permanence&lt;/td&gt;
&lt;td&gt;A fork in your stack. Every new base model is a decision about redoing the work, and providers retire the bases you tuned on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operations&lt;/td&gt;
&lt;td&gt;Hosting, capacity, version pinning, rollback. A prompt change has none of these&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Synthetic data helps with volume and cannot invent the judgement you are trying to capture. If you generate the examples from your own prompt, you have trained an expensive copy of that prompt. LLM-as-judge scales the evaluation, and the judge is a model with its own biases that needs spot checks against an expert. A tuned model also ages. If nobody reruns the comparison against the current base model, the team keeps paying to host a fork that may now do worse than a prompt on the newer model.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fine-tune we did not build
&lt;/h3&gt;

&lt;p&gt;A client came to us wanting a fine-tune for cold outreach email, on a small low-cost model, tuned for open rates. It is a reasonable-sounding request, because writing quality is exactly the kind of thing people expect fine-tuning to fix. We did not tune anything, and splitting the job into two calls fixed it.&lt;/p&gt;

&lt;p&gt;The first call does a short discovery pass on the prospect, and the second writes the email given that research and a handful of examples of what good looks like in their domain. That is two hops instead of one, with no new weights anywhere.&lt;/p&gt;

&lt;p&gt;That is the pattern we keep meeting, where "the output is not good enough" rarely means the model needs different weights. Usually it means the model is being asked to research and write in a single step, with nothing to imitate. Splitting the step and showing examples takes an afternoon, and the fine-tune was going to take a month.&lt;/p&gt;

&lt;p&gt;Fine-tune when the behaviour is unwritten, stable enough to outlive a model generation, and measurable. Otherwise try many-shot prompting first and score it on the eval set. Check the provider's retirement dates before you tune, because a provider can stop training new versions of a tune you still depend on: as of 22 October 2026, OpenAI's &lt;a href="https://developers.openai.com/api/docs/deprecations" rel="noopener noreferrer"&gt;deprecations page&lt;/a&gt; gives 6 January 2027 as the last day existing customers can create fine-tuning jobs. A tune already running on a base with a shutdown date is the case how to replace the model under a production agent walks through.&lt;/p&gt;

&lt;h3&gt;
  
  
  The other reason to fine-tune
&lt;/h3&gt;

&lt;p&gt;The other reason is cost and latency rather than quality. Distillation moves a large model's behaviour on your one task into a small model you can serve at lower cost or run on a device. Judge that on cost per request, because it is a serving decision and the argument above does not apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is agentic RAG, and when do you need it?
&lt;/h2&gt;

&lt;p&gt;Agentic RAG puts retrieval inside an agent loop. The model decides what to search for, reads what came back, and then decides whether to search again or answer, where ordinary RAG runs one search and answers from whatever it returned. We call it agentic search in this guide, because the loop can call any search tool and a vector index is only one of them.&lt;/p&gt;

&lt;p&gt;Single-shot retrieval assumes the question maps to a query, and some questions do not. "Which of our suppliers mentioned tariff exposure in their last two filings?" No query answers that. You need the supplier list, then the filings per supplier, then a read of each, then a decision about when you have enough. An agent that plans a step, acts, reads the result and plans again handles that (plan-and-execute, the ReAct pattern). If one query still answers the question, stay with retrieval. Our guide on agentic workflows vs AI agents separates that case from a scripted workflow and from this loop.&lt;/p&gt;

&lt;p&gt;The loop buys reach with latency and cost, so we add one only when a simpler pipeline has visibly fallen short. Two conditions make it worth the price: the corpus changes faster than an index can follow, and exact search tools already exist for the loop to call. Code meets both. Claude Code's team dropped local vector indexes for model-driven file-name and text search (glob and grep), citing stale indexes and permission complexity (&lt;a href="https://newsletter.pragmaticengineer.com/p/building-claude-code-with-boris-cherny" rel="noopener noreferrer"&gt;Boris Cherny to The Pragmatic Engineer&lt;/a&gt;, March 2026). A large document set that changes monthly and has no structured search meets neither, and retrieval still suits it.&lt;/p&gt;

&lt;p&gt;Our own systems mostly run retrieval inside a fixed sequence of steps. On Prospex, the reasoning steps (business model, customer fit, strategy, risk, tech stack) run in a set order and each one retrieves for itself, so the evidence for every claim stays attached to that claim and two runs on one company are built to give the same read.&lt;/p&gt;

&lt;p&gt;On the executive operations agent, we measured plans completed with no human stepping in rising from roughly 55% to 80% once local retrieval and planner memory grounded the agent in the user's own workspace. The same agent has a critic that chooses continue, retry or escalate after each step, for a different reason: without it, one bad observation carries through every later step and the agent claims success on work it never finished.&lt;/p&gt;

&lt;h3&gt;
  
  
  When is the loop not worth it?
&lt;/h3&gt;

&lt;p&gt;A loop turns one search into several model calls and searches in sequence, and the user waits for every one of them. Ask three questions before you build it.&lt;/p&gt;

&lt;p&gt;Is the question really multi-hop, or is retrieval weak? Many complaints that "the agent needs to search several times" are single-shot retrieval failures nobody diagnosed. Check whether the passage you need ranks near the top for any one query. If it does, fix hybrid search and add reranking, which changes one step of the request, before you add a loop that changes how many steps there are.&lt;/p&gt;

&lt;p&gt;There is a real exception, and we hit it on Prospex when retrieval had run out of road. The source data was structured in a way that resisted tuning: the evidence a single question needed was spread across documents that looked nothing like each other, so better chunking and better hybrid search kept arriving at the same ceiling. An agent that searched, read what came back, and searched again got past that. Another month of retrieval work would not have.&lt;/p&gt;

&lt;p&gt;So the check is not whether you have tried reranking, but whether you have a reason to believe more retrieval work will move the ceiling. On Prospex we did not, and we could say why.&lt;/p&gt;

&lt;p&gt;Is there a latency budget? Rule the loop out for voice before anyone designs it, because the pause a caller expects is short and similar across languages: in a ten-language study, every language's average gap between turns sat within 250 ms of the cross-language mean (&lt;a href="https://www.pnas.org/doi/10.1073/pnas.0903616106" rel="noopener noreferrer"&gt;Stivers et al., PNAS, 2009&lt;/a&gt;). A search loop does not fit in that gap.&lt;/p&gt;

&lt;p&gt;Our insurance voice agent came down from 2.5 to 3 seconds to around 900ms end to end in our tests, and it does not search during a turn at all. Policy and claim context loads when the call opens, so no reply waits on it, and every action goes through a typed tool that checks its inputs and permissions.&lt;/p&gt;

&lt;p&gt;Can the loop stop, and can you show it did the work? An agent that decides when it has enough evidence also decides when to stop. Solve that before the loop ships, because an agent that stops early still writes a confident answer. Our guide on AI agents that call unfinished work done covers the checks. The loop adds reach, and it makes no single search more accurate, so if one good search can answer the question, that search will be faster and easier to debug.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does each layer cost to run?
&lt;/h2&gt;

&lt;p&gt;Two cells in the comparison at the top surprise people. Fine-tuning can lower the cost per call, because a small tuned model that matches a large one on your single task costs less to serve and answers faster. When cost matters more than quality, the usual advice inverts.&lt;/p&gt;

&lt;p&gt;Retrieval's real running cost is freshness, not the search. Keeping the index correct as documents change, permissions move and pages get deleted is the work, and an index that falls behind raises no error, so nobody notices until an answer quotes a page that no longer exists. The multiplied calls in the agentic column are a separate bill, which our guide on AI agent cost splits into the build, the run, and the work that remains after launch.&lt;/p&gt;

&lt;p&gt;Before adding any layer, check prompt caching on a stable system prefix and model routing for the easy requests. Neither one changes what the model does.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does running all three at once look like?
&lt;/h2&gt;

&lt;p&gt;The trademark path, in production since 2023, runs three layers in one request, and every draft is scored before an attorney sees it.&lt;/p&gt;

&lt;p&gt;Remove one and the system fails in a specific way. Without retrieval it invents authorities. Without the fine-tune the drafting reads wrong to a practitioner. Without the schema, the integration breaks the day a model update changes its formatting habits. The evaluation scores each draft from 0 to 10 on six criteria (structure, reasoning depth, citation accuracy, jurisdiction discipline, edge cases and tone), and attorney corrections remain the one signal that automated scoring cannot replace.&lt;/p&gt;

&lt;h2&gt;
  
  
  What will none of this fix?
&lt;/h2&gt;

&lt;p&gt;These four layers get sold as answers to problems they cannot touch. A missing document stays missing, and if the corpus does not contain the answer, retrieval returns the closest wrong thing and generation makes it sound plausible.&lt;/p&gt;

&lt;p&gt;A task nobody has defined stays undefined. If three stakeholders read the spec three ways, a fine-tune learns whichever reading dominated its training examples. Disagreement between experts also stays disagreement. If two specialists mark the same output differently, more training data does not resolve it, and the call belongs to the product owner rather than to the model.&lt;/p&gt;

&lt;p&gt;Bad source data stays bad, whether that means duplicated pages, stale exports or a PDF parser that drops table headers, and every layer downstream inherits it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you decide, in order?
&lt;/h2&gt;

&lt;p&gt;Stop at the first question you cannot answer yes to, and fix that before moving on.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can a new contractor do the task from your prompt? If not, write the specification, add four real examples, and name the refusal condition. An afternoon.&lt;/li&gt;
&lt;li&gt;Do you have twenty to fifty scored eval cases? If not, build them before changing anything else.&lt;/li&gt;
&lt;li&gt;Is the failure a missing or stale fact? Retrieval: hybrid search, reranking, chunking, permissions. Not a bigger model.&lt;/li&gt;
&lt;li&gt;With the right passage in context, is the answer wrong in form rather than fact? That points to fine-tuning, if the convention is unwritten and stable. Try many-shot prompting first and measure it.&lt;/li&gt;
&lt;li&gt;Does answering require several dependent searches? Agentic search, with a stop condition, a verification step and a latency budget you have checked.&lt;/li&gt;
&lt;li&gt;Are you optimising cost or latency rather than quality? Distillation, model routing, prompt caching, judged on cost per request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To find where you are, open your last twenty production failures and label each with one word: unspecified, missing, form, or multi-hop. Count the labels. The largest bucket tells you which step to work on, and it is often not the layer the team was planning to build next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Should I use RAG or fine-tuning?
&lt;/h3&gt;

&lt;p&gt;Use retrieval when the model is missing a fact or is still quoting a document that has changed. Fine-tune when the right passage is already in context and a specialist says the draft reads wrong. Finish the specification, the examples, and the refusal line before either of those, because an unspecified task looks like a model failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  When should you fine-tune instead of using RAG?
&lt;/h3&gt;

&lt;p&gt;Fine-tune for form and for judgement a written rule does not settle, and never to store a fact you will need to update. The authorities and the current documents stay in retrieval. Try many-shot prompting first and score it on your eval set. A tuned model you do not compare with today's base model is a fork you then have to keep.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is agentic RAG?
&lt;/h3&gt;

&lt;p&gt;Agentic RAG, also called agentic search, is a loop that plans a query, reads what came back, and searches again from that result. Use it when no single query can find the answer, such as a supplier list, then each filing, then a read of each one. Stay with one search when that search can answer, because every step of the loop is another model call the user waits for.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between RAG and agentic RAG?
&lt;/h3&gt;

&lt;p&gt;Ordinary RAG runs one search before the model call and answers from what that search returned. Agentic RAG lets the model decide what to search for next, read the result, and search again until it has enough or hits a stop condition. The loop adds reach for questions that need several dependent searches, and it makes no single search more accurate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can RAG and fine-tuning run in the same request?
&lt;/h3&gt;

&lt;p&gt;Yes. Bigcircle's trademark drafting platform has done it in production since 2023. It retrieves from a curated index of authorities, drafts with a fine-tuned model, and constrains the output to a five-section schema whose citations must resolve. Drop retrieval and the draft invents authorities, and drop the fine-tune and a practitioner says the form is wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I choose between prompting, RAG, and fine-tuning?
&lt;/h3&gt;

&lt;p&gt;Stop at the first step you cannot pass. If a new contractor cannot do the task from the prompt, write the specification and add real examples before you touch a model. If the failure is a missing or stale fact, use retrieval, and try many-shot prompting before a fine-tune when the passage is already in context.&lt;/p&gt;

&lt;h3&gt;
  
  
  When is an agent the wrong way to search?
&lt;/h3&gt;

&lt;p&gt;Skip the agent when one query can still answer the question. Many complaints that the system must search several times are single-shot retrieval misses, fixed by hybrid search and reranking without adding steps. A voice product answering inside a conversational pause cannot host the loop, so Bigcircle's insurance voice agent loads its context when the call opens and does not search during a turn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Anthropic, &lt;a href="https://www.anthropic.com/engineering/contextual-retrieval" rel="noopener noreferrer"&gt;Introducing contextual retrieval&lt;/a&gt;, September 2024. Top-20 retrieval failure from 5.7% to 1.9%.&lt;/li&gt;
&lt;li&gt;Gergely Orosz, &lt;a href="https://newsletter.pragmaticengineer.com/p/building-claude-code-with-boris-cherny" rel="noopener noreferrer"&gt;Building Claude Code with Boris Cherny&lt;/a&gt;, The Pragmatic Engineer, March 2026. Why Claude Code dropped vector indexes for glob and grep.&lt;/li&gt;
&lt;li&gt;Stivers et al., &lt;a href="https://www.pnas.org/doi/10.1073/pnas.0903616106" rel="noopener noreferrer"&gt;Universals and cultural variation in turn-taking in conversation&lt;/a&gt;, PNAS, 2009.&lt;/li&gt;
&lt;li&gt;OpenAI, &lt;a href="https://developers.openai.com/api/docs/deprecations" rel="noopener noreferrer"&gt;deprecations&lt;/a&gt;, checked 22 October 2026. The fine-tuning cut-off date.&lt;/li&gt;
&lt;li&gt;Why RAG systems fail in production. The retrieval layer in depth, failure by failure.&lt;/li&gt;
&lt;li&gt;The practical guide to AI agents. What makes a loop controllable rather than just autonomous.&lt;/li&gt;
&lt;li&gt;Agentic workflows vs AI agents. Whether the task needs this loop, a scripted workflow, or retrieval on its own.&lt;/li&gt;
&lt;li&gt;Why AI agents report unfinished work as done. The verification problem agentic search inherits.&lt;/li&gt;
&lt;li&gt;How LLM inference actually works. Why prefill, decode and caching drive the cost column above.&lt;/li&gt;
&lt;li&gt;Case studies referenced: trademark drafting, prospect intelligence, ESG filings, executive operations, insurance voice.&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bigcircle.ai/guides/rag-vs-fine-tuning-vs-agentic-search?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rag-vs-fine-tuning-vs-agentic-search" rel="noopener noreferrer"&gt;bigcircle.ai&lt;/a&gt;. I'm Tirth, founder of &lt;a href="https://bigcircle.ai/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=rag-vs-fine-tuning-vs-agentic-search" rel="noopener noreferrer"&gt;BigCircle&lt;/a&gt;, where we build production AI systems with governance built in.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>rag</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>GPT-4o deprecation: dates, replacement and how to migrate</title>
      <dc:creator>Tirth Gajjar</dc:creator>
      <pubDate>Thu, 29 Oct 2026 18:40:20 +0000</pubDate>
      <link>https://dev.to/bigcircle/gpt-4o-deprecation-dates-replacement-and-how-to-migrate-3ibb</link>
      <guid>https://dev.to/bigcircle/gpt-4o-deprecation-dates-replacement-and-how-to-migrate-3ibb</guid>
      <description>&lt;p&gt;GPT-4o leaves the OpenAI API on 14 January 2027. As of 22 October 2026, that date covers the listed GPT-4o, GPT-4o mini, GPT-4.1 and GPT-4.1 mini snapshots and the fine-tunes built on them, and OpenAI names &lt;code&gt;gpt-6-luna&lt;/code&gt; as the replacement. Azure retires most of the same versions on 14 April 2027. ChatGPT dropped GPT-4o in February 2026, which did not touch the API.&lt;/p&gt;

&lt;p&gt;Changing the model string takes a minute, but proving the product still behaves takes the rest of the migration, because Luna handles tool calls, reasoning and sampling differently from the model your evals were scored on. This guide gives the dates, then the order of checks we recommend for this swap and every one after it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When is GPT-4o deprecated on the OpenAI API, Azure and ChatGPT?
&lt;/h2&gt;

&lt;p&gt;Plan against the earliest date that touches any id you call. Each platform runs its own schedule, and a team that reads one page and assumes it covers the others plans for the wrong day.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Where the model is called&lt;/th&gt;
&lt;th&gt;Shutdown date&lt;/th&gt;
&lt;th&gt;Listed replacement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI API&lt;/td&gt;
&lt;td&gt;14 January 2027 for &lt;code&gt;gpt-4o-2024-08-06&lt;/code&gt;, &lt;code&gt;gpt-4o-2024-11-20&lt;/code&gt;, &lt;code&gt;gpt-4o-mini-2024-07-18&lt;/code&gt;, &lt;code&gt;gpt-4.1-2025-04-14&lt;/code&gt;, &lt;code&gt;gpt-4.1-mini-2025-04-14&lt;/code&gt;, and fine-tunes on them&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gpt-6-luna&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI API, earlier batch&lt;/td&gt;
&lt;td&gt;23 October 2026 for &lt;code&gt;gpt-4o-2024-05-13&lt;/code&gt; and &lt;code&gt;gpt-4.1-nano&lt;/code&gt;, including its fine-tune&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gpt-5.6-sol&lt;/code&gt; for the GPT-4o snapshot, &lt;code&gt;gpt-5.6-luna&lt;/code&gt; for nano&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure OpenAI&lt;/td&gt;
&lt;td&gt;9 December 2026 for &lt;code&gt;gpt-4o&lt;/code&gt; 2024-05-13. 14 April 2027 for &lt;code&gt;gpt-4o&lt;/code&gt; 2024-08-06 and 2024-11-20, &lt;code&gt;gpt-4o-mini&lt;/code&gt;, and all three GPT-4.1 sizes&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;gpt-5.6-sol&lt;/code&gt; for 2024-05-13, &lt;code&gt;gpt-5.1&lt;/code&gt; for the later GPT-4o versions, none listed for GPT-4.1 or &lt;code&gt;gpt-4o-mini&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Azure OpenAI, fine-tuned deployments&lt;/td&gt;
&lt;td&gt;1 October 2027 for GPT-4o and GPT-4o mini fine-tunes, 14 October 2027 for GPT-4.1 fine-tunes&lt;/td&gt;
&lt;td&gt;Training ends earlier, no earlier than April 2027 for existing customers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ChatGPT&lt;/td&gt;
&lt;td&gt;Retired 13 February 2026&lt;/td&gt;
&lt;td&gt;Not an API change&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Dates are as listed on OpenAI's deprecations page and Microsoft's retirement schedule on 22 October 2026. Any call still pinned to the 23 October batch should already be on its replacement, scored on live traffic from the first day.&lt;/p&gt;

&lt;p&gt;Size your retest window to three months, not six. OpenAI's policy promises at least six months' notice for a generally available model, with less allowed for a specialized variant or for safety and compliance reasons. The GPT-4 series notice went out on 14 October 2026 for a 14 January 2027 shutdown, three months, so a plan that assumed six has already lost half its time.&lt;/p&gt;

&lt;p&gt;On Azure, set &lt;code&gt;versionUpgradeOption&lt;/code&gt; deliberately on every deployment. Standard, Data Zone Standard and Global Standard deployments are upgraded automatically at retirement, so a &lt;code&gt;gpt-4o&lt;/code&gt; 2024-05-13 deployment becomes &lt;code&gt;gpt-5.6-sol&lt;/code&gt; on 9 December 2026 with no code change. &lt;code&gt;NoAutoUpgrade&lt;/code&gt; makes the deployment stop working at retirement instead. Provisioned deployments, which reserve fixed capacity, are never upgraded and have to be moved by hand. Score an automatic upgrade exactly as you would score a change you chose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does a passing eval on GPT-4o not carry over to the replacement?
&lt;/h2&gt;

&lt;p&gt;Treat the replacement as a new system and score it from the start, even when the prompt is identical. The agent can pick a different tool or fill a field with a different value. A tool schema can stay valid while the arguments change shape, and a structured-output schema can still parse while one field now means something else. One good completion on Luna shows none of that.&lt;/p&gt;

&lt;p&gt;We design for this before any migration is planned. The &lt;a href="https://bigcircle.ai/work/trademark-drafting-fine-tuned-llm?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=swap-llm-model-in-production" rel="noopener noreferrer"&gt;trademark drafting system&lt;/a&gt;, in production with a client, logs the base model and the fine-tuned model on the same inputs against a 0 to 10 legal-integrity harness on six axes, and every draft passes that harness before an attorney sees it. A regression from a model change appears as a score movement, before an attorney reads the draft.&lt;/p&gt;

&lt;p&gt;Our own sales roleplay coach takes the other half of the idea. Its score comes from a versioned rubric with weighted dimensions and behavioural markers, and the model only writes the qualitative read. Raw model scores drift between sessions, conversation lengths and personas. If the model also set the number, an upgrade would move every historical comparison, and nobody could tell a better rep from a different model.&lt;/p&gt;

&lt;p&gt;Survey data points the same way: in VentureBeat's September 2026 survey of 109 opt-in respondents, 59 percent of the companies that replaced a model in a production app saw performance drop and reworked the app or went back to the original. Read it as a reason to score your own cases, not as a rate to plan around.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you run LLM regression testing before the model id changes?
&lt;/h2&gt;

&lt;p&gt;Run the same eval cases on the current model and the replacement, each case several times, and only then move traffic in stages. The order we recommend is the same whatever the vendor, and each row below names what to record and what fails if the step is skipped.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What to record&lt;/th&gt;
&lt;th&gt;What fails if it is skipped&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inventory&lt;/td&gt;
&lt;td&gt;Every model id in code, config and stored jobs, including dated snapshots, fine-tune ids and Azure deployment names&lt;/td&gt;
&lt;td&gt;A forgotten snapshot stops answering on the shutdown date while the tests stay green&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pin&lt;/td&gt;
&lt;td&gt;The replacement id, written into the eval report beside the id it replaces&lt;/td&gt;
&lt;td&gt;An alias that moves later is a second change nobody scored&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same cases&lt;/td&gt;
&lt;td&gt;The existing eval set, run on the current model and on the replacement&lt;/td&gt;
&lt;td&gt;A score with no pair cannot show a regression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeats&lt;/td&gt;
&lt;td&gt;A pass rate where wording may vary, and a pass on every repeat for a safety case&lt;/td&gt;
&lt;td&gt;One green run hides a completion that will not come back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contracts&lt;/td&gt;
&lt;td&gt;Tool name, arguments and the structured-output schema, checked on their own&lt;/td&gt;
&lt;td&gt;The prose still reads well while the integration stops parsing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shadow&lt;/td&gt;
&lt;td&gt;The new id run on live inputs, scored, with no action taken&lt;/td&gt;
&lt;td&gt;The canary becomes the first time the live mix of requests appears&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canary&lt;/td&gt;
&lt;td&gt;A slice of traffic, with the old id still callable&lt;/td&gt;
&lt;td&gt;A full cutover has no rollback once config has forgotten the old id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Tokens, reasoning tokens and cache writes, on your own traces&lt;/td&gt;
&lt;td&gt;A lower token price on a longer trace can still cost more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retire&lt;/td&gt;
&lt;td&gt;The old id removed from the inventory once the canary holds&lt;/td&gt;
&lt;td&gt;The next person to read the config puts back an id that is about to stop answering&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;OpenAI's own migration note has four steps: record a baseline of quality, latency and cost, evaluate Luna starting at reasoning effort &lt;code&gt;none&lt;/code&gt;, tune and retest, then send a limited share of production traffic. All four sit inside this table, so add the two rows it leaves out. A baseline that stores one completion per case cannot give you a pass rate, and a prose score cannot show that a tool call changed shape.&lt;/p&gt;

&lt;p&gt;Score each case more than once, because a hosted model does not return the same answer twice even at temperature 0. Why LLM temperature 0 is not deterministic explains why a safety case has to pass on every repeat while a quality case ships on its pass rate. Take the cases from failures your team has already read, as our guide to &lt;a href="https://bigcircle.ai/guides/how-to-build-llm-evals?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=swap-llm-model-in-production" rel="noopener noreferrer"&gt;LLM evaluation&lt;/a&gt; sets out, rather than writing new ones for the migration.&lt;/p&gt;

&lt;p&gt;Move the eval set itself out of OpenAI's platform if it lives there. Existing evals on the Evals dashboard become read-only on 31 October 2026, and the Evals API, reusable prompt objects and the &lt;code&gt;v1/prompts&lt;/code&gt; API shut down on 30 November 2026. Keep prompts and cases in your own repository, where the next migration can rerun them.&lt;/p&gt;

&lt;p&gt;Keep the old id callable until the canary's scores hold, or until the shutdown date removes the choice. Deleting it from config on the day the evals pass leaves no way back if the next day's traffic is a different mix from the one you scored.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does GPT-6 Luna change in a GPT-4o request?
&lt;/h2&gt;

&lt;p&gt;Start the migration at reasoning effort &lt;code&gt;none&lt;/code&gt;, on the endpoint you already use. It keeps the request closest to the old one, and on Chat Completions it is the only setting where tools still work. Then test higher settings against quality, latency and cost.&lt;/p&gt;

&lt;p&gt;Reasoning effort on Luna runs from &lt;code&gt;none&lt;/code&gt; through &lt;code&gt;low&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt;, &lt;code&gt;high&lt;/code&gt;, &lt;code&gt;xhigh&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt;, and the default is &lt;code&gt;medium&lt;/code&gt;. A request that sends no setting therefore starts reasoning, which the GPT-4o call never did, and reasoning tokens bill as output.&lt;/p&gt;

&lt;p&gt;Tool calls move endpoint once reasoning is on. Luna accepts function calling on &lt;code&gt;v1/chat/completions&lt;/code&gt; only at reasoning effort &lt;code&gt;none&lt;/code&gt;, and reasoning combined with tools goes through the Responses API. GPT-6 Astra and GPT-6.1 Sol need Responses for any tool call. Chat Completions has no shutdown date, but plan the move to Responses now for any route that will need reasoning and tools together.&lt;/p&gt;

&lt;p&gt;Sampling settings go too: at any reasoning effort other than &lt;code&gt;none&lt;/code&gt;, the request has to drop &lt;code&gt;temperature&lt;/code&gt;, &lt;code&gt;top_p&lt;/code&gt; and &lt;code&gt;top_logprobs&lt;/code&gt;, plus &lt;code&gt;logprobs&lt;/code&gt; on Chat Completions. A prompt that relied on a low temperature to hold its format loses that control, so the format can move with nothing on the request to explain it. Check the structured-output contract on its own at every reasoning level you test.&lt;/p&gt;

&lt;p&gt;Compare total cost on your own traces, not the price per token. As of 22 October 2026 Luna lists $0.10 per million input tokens and $0.50 per million output, with cached input at $0.01 and cache writes at $0.125. A prompt over 272,000 input tokens is billed at twice the input and cache rates and 1.5 times the output rate for the whole request. The context window is 1,050,000 tokens, so a retrieval prompt that sat under GPT-4o's limit can now cross that price step with no change to the chunker. What a production AI agent costs works through how a longer trace shows up on the bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens to GPT-4o fine-tunes?
&lt;/h2&gt;

&lt;p&gt;Work out what each fine-tune is holding before 6 January 2027, because the replacement OpenAI names cannot be fine-tuned. On the OpenAI API, fine-tunes on the January bases shut down with the base model on 14 January 2027, and from 6 January existing customers can no longer create fine-tuning jobs. As of 22 October 2026 the GPT-6 Luna model page lists the fine-tuning endpoint as not supported.&lt;/p&gt;

&lt;p&gt;Do not start a fresh job on a GPT-4o base before the cutoff, because a model trained in late December is one you have to leave again in January. Use the eight days between 6 and 14 January as an overlap, with the replacement scored beside the fine-tune that still answers.&lt;/p&gt;

&lt;p&gt;Sort each behaviour the tune carries by where it can live next. On the trademark system, fine-tuning carries the reasoning patterns, section boundaries and tone of an attorney's opinion, and it does not handle citation integrity. Citations come from retrieval over an approved index of case law, statutes and registry data, and validators check the five-section structure. A change of base model there would mean retraining tone and structure only, because the citations never lived in the weights.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bigcircle.ai/guides/rag-vs-fine-tuning-vs-agentic-search?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=swap-llm-model-in-production" rel="noopener noreferrer"&gt;RAG vs fine-tuning vs agentic search&lt;/a&gt; is the test for each behaviour. A tune that was standing in for examples can often be replaced by examples in the prompt. A behaviour that has to outlive this base needs a base you can still train and serve. An open-weight model, one whose weights you can download and host, is that path only after the same eval set passes on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where should rules live so they survive a model swap?
&lt;/h2&gt;

&lt;p&gt;Put sums, permissions and thresholds in code, not in the prompt or the weights. On our insurance voice agent, a required disclosure is a condition in the tool contract, so the question "can this payment run without the disclosure?" has the answer no whatever model sits behind it. A rule enforced in code carries across a migration untouched. A rule written into the prompt has to be retested on every model you try.&lt;/p&gt;

&lt;p&gt;Keep the model id behind an interface the application owns, so a swap edits one place and no business logic. Our &lt;a href="https://bigcircle.ai/work/executive-ops-desktop-agent?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=swap-llm-model-in-production" rel="noopener noreferrer"&gt;desktop chief-of-staff agent&lt;/a&gt; calls the model as a reasoning service and keeps credentials, the tool registry and execution on the user's machine. A second provider belongs behind the same interface as a tested option, not as an extra hop every request depends on, because every vendor in one flow has to be up for that flow to run.&lt;/p&gt;

&lt;p&gt;Model routing can still send an easy case to a smaller model. Retune the threshold whenever either model changes, because a route left on the old threshold changes which model acts with no alert, and the regression gets blamed on the wrong file. TypeSafe's Jev, a typed decision model built for routing and other bounded decisions, keeps that choice outside the generative model; how to introduce Jev into agentic workflows covers where it fits.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you see the next OpenAI model deprecation in time?
&lt;/h2&gt;

&lt;p&gt;Make the next shutdown date fail a check while the id still answers, instead of arriving as an email. The 14 January date is one row on a page that already lists later ones, including GPT-5.1, GPT-5.3-Codex and GPT-5.4 nano on 1 April 2027.&lt;/p&gt;

&lt;p&gt;On the OpenAI API, call &lt;code&gt;GET /v1/models&lt;/code&gt; and read each model's optional &lt;code&gt;shutdown_date&lt;/code&gt;, which the retrieve-a-model endpoint returns as well.&lt;/p&gt;

&lt;p&gt;On Azure, read &lt;code&gt;lifecycleStatus&lt;/code&gt; and &lt;code&gt;deprecation.inference&lt;/code&gt; from the Models API, and mind the naming. A model the docs call deprecated, still serving existing customers, shows in the API as &lt;code&gt;Deprecating&lt;/code&gt;. The API value &lt;code&gt;Deprecated&lt;/code&gt; means the model is already retired and every call returns &lt;code&gt;410 Gone&lt;/code&gt;. Alert on &lt;code&gt;Deprecating&lt;/code&gt;, because a check that waits for &lt;code&gt;Deprecated&lt;/code&gt; fires after the outage.&lt;/p&gt;

&lt;p&gt;Run that read on a schedule against the ids in your inventory, and fail the check when a date falls inside your retest window, which the GPT-4 notice suggests should be three months. Send the failure as a pull request or a page to the person who owns the model, while there is still time to run the table above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  When is GPT-4o deprecated on the OpenAI API?
&lt;/h3&gt;

&lt;p&gt;As of 22 October 2026, OpenAI's deprecations page shuts down gpt-4o-2024-08-06, gpt-4o-2024-11-20, gpt-4o-mini-2024-07-18, gpt-4.1-2025-04-14 and gpt-4.1-mini-2025-04-14 on 14 January 2027, with fine-tunes on those bases on the same day. The older gpt-4o-2024-05-13 snapshot and gpt-4.1-nano shut down on 23 October 2026. New fine-tuning jobs end on 6 January 2027.&lt;/p&gt;

&lt;h3&gt;
  
  
  What replaces GPT-4o?
&lt;/h3&gt;

&lt;p&gt;On the OpenAI API, gpt-6-luna replaces the January 2027 GPT-4o, GPT-4o mini, GPT-4.1 and GPT-4.1 mini snapshots, and gpt-5.6-sol replaces the older gpt-4o-2024-05-13. Azure lists gpt-5.6-sol for 2024-05-13 and gpt-5.1 for the later GPT-4o versions, and names no replacement for GPT-4.1 or gpt-4o-mini. Score the replacement on your own cases before you move traffic.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens to GPT-4o fine-tuned models?
&lt;/h3&gt;

&lt;p&gt;On the OpenAI API they shut down with their base model, on 14 January 2027 for the listed snapshots, and existing customers cannot create new fine-tuning jobs from 6 January 2027. GPT-6 Luna does not support fine-tuning. On Azure, GPT-4o and GPT-4o mini fine-tuned deployments run until 1 October 2027 and GPT-4.1 fine-tunes until 14 October 2027.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are the Azure OpenAI retirement dates the same?
&lt;/h3&gt;

&lt;p&gt;No. Microsoft's schedule, as of 22 October 2026, retires gpt-4o 2024-05-13 on 9 December 2026 and the later GPT-4o versions, gpt-4o-mini and all three GPT-4.1 sizes on 14 April 2027. Standard deployments are upgraded to the listed replacement automatically at retirement unless versionUpgradeOption says otherwise, and provisioned deployments are not upgraded at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is GPT-4o still in ChatGPT?
&lt;/h3&gt;

&lt;p&gt;No. OpenAI retired GPT-4o, GPT-4.1, GPT-4.1 mini and o4-mini from ChatGPT on 13 February 2026, and said at the time that nothing changed in the API. The ChatGPT date and the API date are separate decisions, so a team calling the API should plan against the deprecations page.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you test an LLM before switching models?
&lt;/h3&gt;

&lt;p&gt;Run the same eval cases on the current model and the replacement, several times each, and check tool-call arguments and structured-output schemas on their own. Then run the new model in shadow on live inputs, send it a canary slice of traffic, and keep the old id callable until the canary's scores hold. A single green run on the new model is one sample.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Chat Completions still call tools on GPT-6 Luna?
&lt;/h3&gt;

&lt;p&gt;Only at reasoning effort none. Reasoning together with tool calls uses the Responses API, and GPT-6 Astra and GPT-6.1 Sol need Responses for any tool call. At any other reasoning effort, temperature, top_p, top_logprobs and logprobs have to be removed from the request. OpenAI suggests starting most GPT-4.x workloads at none.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you find out when an OpenAI model will be deprecated?
&lt;/h3&gt;

&lt;p&gt;Call GET /v1/models and read shutdown_date on each id you use. It is a year-month-day string once a date is announced and null before that. On Azure, read lifecycleStatus and deprecation.inference from the Models API, where Deprecating means a date is set and Deprecated means already retired. Fail a scheduled check while the id still answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How to build an LLM evaluation from real failures. The case set a migration reruns.&lt;/li&gt;
&lt;li&gt;Why LLM temperature 0 is not deterministic. Repeat-run pass rates, and why a safety case passes on every repeat.&lt;/li&gt;
&lt;li&gt;What a production AI agent costs. Why a lower token price can still raise the bill.&lt;/li&gt;
&lt;li&gt;RAG vs fine-tuning vs agentic search. Which behaviour a fine-tune was holding.&lt;/li&gt;
&lt;li&gt;OpenAI, &lt;a href="https://developers.openai.com/api/docs/deprecations" rel="noopener noreferrer"&gt;API deprecations&lt;/a&gt;, read 22 October 2026. Shutdown dates, the notice policy, the fine-tuning cutoff and the &lt;code&gt;shutdown_date&lt;/code&gt; field.&lt;/li&gt;
&lt;li&gt;Microsoft, &lt;a href="https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/model-retirement-schedule" rel="noopener noreferrer"&gt;Model retirement schedule&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/azure/ai-foundry/openai/concepts/model-retirements" rel="noopener noreferrer"&gt;Foundry Models lifecycle and support policy&lt;/a&gt;, read 22 October 2026. Azure dates, automatic upgrades and the Models API status values.&lt;/li&gt;
&lt;li&gt;OpenAI, &lt;a href="https://developers.openai.com/api/docs/models/gpt-6-luna" rel="noopener noreferrer"&gt;GPT-6 Luna&lt;/a&gt;, &lt;a href="https://developers.openai.com/api/docs/guides/latest-model" rel="noopener noreferrer"&gt;Using GPT-6&lt;/a&gt; and &lt;a href="https://help.openai.com/en/articles/20001600-migrating-from-gpt-4x-to-gpt-6-luna" rel="noopener noreferrer"&gt;Migrating from GPT-4.x to GPT-6 Luna&lt;/a&gt;. Reasoning effort, tool calling by endpoint, parameters to remove and prices.&lt;/li&gt;
&lt;li&gt;OpenAI, &lt;a href="https://openai.com/index/retiring-gpt-4o-and-older-models" rel="noopener noreferrer"&gt;Retiring GPT-4o and older models&lt;/a&gt;, 29 January 2026. The ChatGPT retirement.&lt;/li&gt;
&lt;li&gt;VentureBeat, &lt;a href="https://venturebeat.com/orchestration/openais-ai-agent-platform-is-used-by-twice-as-many-companies-as-anthropics-new-venturebeat-survey-finds" rel="noopener noreferrer"&gt;agent orchestration survey&lt;/a&gt;, 13 October 2026. The 59 percent figure.&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bigcircle.ai/guides/swap-llm-model-in-production?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=swap-llm-model-in-production" rel="noopener noreferrer"&gt;bigcircle.ai&lt;/a&gt;. I'm Tirth, founder of &lt;a href="https://bigcircle.ai/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=swap-llm-model-in-production" rel="noopener noreferrer"&gt;BigCircle&lt;/a&gt;, where we build production AI systems with governance built in.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>openai</category>
      <category>testing</category>
    </item>
    <item>
      <title>Agentic workflows vs AI agents, and when not to build one</title>
      <dc:creator>Tirth Gajjar</dc:creator>
      <pubDate>Tue, 27 Oct 2026 18:40:19 +0000</pubDate>
      <link>https://dev.to/bigcircle/agentic-workflows-vs-ai-agents-and-when-not-to-build-one-31f</link>
      <guid>https://dev.to/bigcircle/agentic-workflows-vs-ai-agents-and-when-not-to-build-one-31f</guid>
      <description>&lt;p&gt;An agentic workflow is a multi-step process in which a language model does part of the work, such as drafting, classifying, retrieving or calling a tool. The term covers two designs: in one, code you wrote picks every next step, and in the other the model picks it, a design most people call an AI agent.&lt;/p&gt;

&lt;p&gt;In this guide a workflow is the first design and an agent is the second. The question for a build is which of those your task needs, and the answer comes from the path the work takes rather than from the label on the ticket.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Scripted workflow&lt;/th&gt;
&lt;th&gt;RAG or search&lt;/th&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fixed path&lt;/td&gt;
&lt;td&gt;Use it. The code walks the steps you listed.&lt;/td&gt;
&lt;td&gt;Use it when the path is one query and one answer.&lt;/td&gt;
&lt;td&gt;Skip it. The model would re-choose steps you already listed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-step judgment&lt;/td&gt;
&lt;td&gt;Use it only when every branch is a rule you can write.&lt;/td&gt;
&lt;td&gt;A retrieved passage does not choose the next action.&lt;/td&gt;
&lt;td&gt;Use it when the next judgment depends on the last result.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool recovery needed&lt;/td&gt;
&lt;td&gt;Use it when the retry is the same step, or a known fallback.&lt;/td&gt;
&lt;td&gt;A search miss is a retrieval problem. Fix the index.&lt;/td&gt;
&lt;td&gt;Use it when the error has to choose a different tool.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High blast radius, no gate&lt;/td&gt;
&lt;td&gt;Keep the write out. The read path can stay in code.&lt;/td&gt;
&lt;td&gt;Keep a permission filter on the query.&lt;/td&gt;
&lt;td&gt;Do not start. The loop does not contain a bad write.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Measurable success&lt;/td&gt;
&lt;td&gt;Score the output of each step.&lt;/td&gt;
&lt;td&gt;Score recall, and whether the answer stays inside the passage.&lt;/td&gt;
&lt;td&gt;Score the stop, and a final state outside the model's claim.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How Bigcircle handles this&lt;/td&gt;
&lt;td&gt;On the fund reconciliation build, the matching rules are explicit and versioned in code, and a person approves only the resolution of an exception.&lt;/td&gt;
&lt;td&gt;On Prospex, semantic and keyword search run together, because their failure modes are complementary.&lt;/td&gt;
&lt;td&gt;On the chief-of-staff build, the agent picks the next tool from the last result, and a critic decides whether to continue, retry or escalate.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most of what we have built is a workflow in that sense, by design. Of the ten systems in our case studies, the ones that turn filings, custodian feeds, legal drafts or meeting audio into records run as fixed pipelines, and where a model appears it sits inside one step. They are the ESG filing pipeline, the &lt;a href="https://bigcircle.ai/work/fund-operations-reconciliation?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=agentic-workflows-vs-ai-agents" rel="noopener noreferrer"&gt;fund reconciliation system&lt;/a&gt;, the trademark drafting system and the meeting transcription app.&lt;/p&gt;

&lt;p&gt;Even the &lt;a href="https://bigcircle.ai/work/devops-agent-live-aws?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=agentic-workflows-vs-ai-agents" rel="noopener noreferrer"&gt;DevOps agent&lt;/a&gt; turns each request into a structured spec and an explicit chain of tool calls, because a reviewer can only build judgement about output whose shape is stable. The chief-of-staff agent needed a loop. It works across more than a hundred registered systems, much of it behind logins with no usable API, and each step reads what the last one returned before choosing the next.&lt;/p&gt;

&lt;p&gt;A demo hides the difference. Each shape can call a model, show a tool call and return a sentence that sounds finished. The split shows when a step fails or a record is missing. A workflow stops on the branch you wrote and retrieval says the passage is absent. An agent has to choose another tool, or stop, based on what it just saw.&lt;/p&gt;

&lt;p&gt;If you cannot point at a case where that choice is real, the loop is overhead you will spend the next quarter tracing. This guide settles the vocabulary first, then takes the three shapes in the order a team usually reaches for them and puts five signals in one matrix. The last two sections cover the signals teams most often skip. One is a write that can do damage before any gate exists, and the other is a success check that only the model can see.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an agentic workflow, and how is it different from an AI agent?
&lt;/h2&gt;

&lt;p&gt;The phrase is used both ways, so ask which one a vendor or a colleague means before you compare designs. Most of the pages that rank for it mean the second design, a model choosing the next step while it runs. Anthropic's &lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Building effective agents&lt;/a&gt; calls that same system an agent and keeps "workflow" for code paths written in advance. Two proposals can both say "agentic workflow" and describe opposite systems, and the difference decides how you test them.&lt;/p&gt;

&lt;p&gt;The usual list of workflow patterns is chaining one call into the next, routing to a branch, running calls in parallel, an orchestrator handing out subtasks, and one call grading another's draft. Review all but one of them as workflows, because code still owns the order.&lt;/p&gt;

&lt;p&gt;The orchestrator is the exception: a lead model chooses the subtasks, so part of the path is already the model's choice, and it needs the checks of an agent. Multi-agent systems covers that pattern once the workers are agents themselves.&lt;/p&gt;

&lt;p&gt;We keep those words because they name the decision you have to make. A scripted workflow is code picking the next step, with a model inside some of the steps. An agent is the model picking it. Plain automation, such as a rules engine, has no model at all, and the next section shows how far that gets.&lt;/p&gt;

&lt;p&gt;When a proposal uses the phrase "agentic workflow", ask which step the model chooses and what code checks the choice. If the model chooses none, you are looking at a scripted workflow with model calls in it, which is usually the right thing to build. If it chooses the next tool, the rest of this guide applies before you sign off on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When is automation or a scripted workflow enough?
&lt;/h2&gt;

&lt;p&gt;Rule-based automation and a scripted workflow both keep the path in code, and an AI agent does not. A scripted workflow is the right shape when someone who knows the job can write the steps, including the branches. Invoice extraction, support triage with a fixed set of labels and document classification usually sit here. The code walks classify, retrieve, draft, check and send, a model fills the steps that need language, and the step after each model call is the one you named.&lt;/p&gt;

&lt;p&gt;The fund reconciliation system we built shows how far rules go. Custodians agree on nothing, not the format, the field names or the sign conventions, so each custodian gets its own mapping rather than one parser full of conditionals. The matching rules are explicit and versioned, because they are the domain knowledge. A person approves at one step only, resolving an exception, because that is where human judgement enters the data. There is no model call anywhere on that path.&lt;/p&gt;

&lt;p&gt;The common advice runs the other way. OpenAI's &lt;a href="https://cdn.openai.com/business-guides-and-resources/a-practical-guide-to-building-agents.pdf" rel="noopener noreferrer"&gt;practical guide to building agents&lt;/a&gt; points agents at rule sets that have grown unwieldy and costly to update. Take that advice when nobody can state the rules. When the rules are the domain knowledge, as they are in reconciliation, versioning them in code costs less than asking a model to rediscover them on each run, and an auditor can read the version that applied.&lt;/p&gt;

&lt;p&gt;Put an agent on a path like that and the model spends the run re-deciding a sequence the code already had. A failure then looks like a choice, so you open the trace, when the missing piece was a branch nobody wrote. With the order of steps in code, a reviewer can point at the branch that fired.&lt;/p&gt;

&lt;p&gt;The trademark drafting system is a pipeline for a different reason. Each stage has a defined failure behaviour, and a failed validation triggers a bounded retry that may draw only on retrieved evidence. Falling back to unconstrained generation is the most common way we see systems of that shape go wrong. A loop that could choose its own next step would reopen that path.&lt;/p&gt;

&lt;p&gt;A branch you can state as a rule is still a workflow. If the amount is under the cap and the order is inside the return window, draft the refund and wait for a person to approve it. The model can draft the note while the cap stays in the code that checks the amount. A value you can compute is computed in code and passed in.&lt;/p&gt;

&lt;h2&gt;
  
  
  When is retrieval enough on its own?
&lt;/h2&gt;

&lt;p&gt;Retrieval is enough when the failure is a missing or stale fact and one query can find the passage that holds it. A refund window that changed in March, a policy clause, or a filing the model was never trained on are RAG problems, or plain search problems. An agent does not repair a weak index, and RAG vs fine-tuning walks those layers in the order they cost.&lt;/p&gt;

&lt;p&gt;Prospex, our own research engine, has to answer both "how do they make money", which is a semantic question, and "are they using Snowflake", which is a keyword question. Pure vector search will happily return a passage about a different warehouse. So semantic and keyword search run together rather than as a fallback chain, because their failure modes are complementary. A custom chunker keeps a role and its company in the same chunk.&lt;/p&gt;

&lt;p&gt;A loop that searches until it feels sure calls the same weak index several times and then writes a fluent answer over thin evidence. You pay for several model calls to hide a recall problem that one better query would have shown in the log. On Prospex, too little retrieval produces a short profile, which is the safe failure, because a short profile is visibly short and an invented one is not.&lt;/p&gt;

&lt;p&gt;Some questions do need more than one retrieval. The ESG pipeline gets comparative research questions, such as one about "the same subsidiary two years earlier", which pure vector search cannot express. Pure graph traversal fails the other way, because it cannot find a narrative claim that was never structured. One retrieval cannot answer a comparison, so the question is broken into steps: select sources, combine graph and vector evidence, build claims, check them and attach the source page to each one.&lt;/p&gt;

&lt;p&gt;On the ESG pipeline that is a sequence of named steps: several retrievals in a known order, not a model left to decide what to search next. If you can name the second query the first result forces, you can often write it as a step. An agent loop starts to pay only when the next query depends on what came back in a way you cannot list.&lt;/p&gt;

&lt;h2&gt;
  
  
  When do you need an AI agent instead of a workflow?
&lt;/h2&gt;

&lt;p&gt;An agent earns its cost when the next action depends on an observation you could not encode as a branch. The model selects a tool, reads the result, and either continues or stops. How to build an AI agent covers that loop, and this guide covers the decision to build it.&lt;/p&gt;

&lt;p&gt;On the chief-of-staff agent each step runs think, act, observe, and the raw observation goes back into the loop rather than being summarised away. A critic then decides whether to continue, retry or escalate. Without it, one bad observation spreads through every later step and the agent claims success on work it never completed.&lt;/p&gt;

&lt;p&gt;The model is not handed the whole tool registry either. It queries the registry and chooses among a handful of relevant contracts, because past roughly 30 tools in the prompt it started picking plausible but wrong ones.&lt;/p&gt;

&lt;p&gt;Tool recovery is where the two shapes part. Repeating a call that timed out is still a retry inside a workflow. A not-found error that means "try the archive, and if that is empty, ask for the account id" is a choice among tools. If every recovery you care about is already a row in a table, keep it in the table. Start the loop only when the error has to pick the tool.&lt;/p&gt;

&lt;p&gt;A miss inside the loop runs longer than it would in a workflow. A wrong branch fails one step, while a wrong choice can take several more tool calls before anyone sees a bad final state.&lt;/p&gt;

&lt;p&gt;The loop pays for its flexibility in latency and tokens, and &lt;a href="https://bigcircle.ai/guides/ai-agent-cost?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=agentic-workflows-vs-ai-agents" rel="noopener noreferrer"&gt;AI agent cost&lt;/a&gt; shows how each extra turn resends the transcript. The loop needs a budget, a stop rule and a check that does not trust the model's claim that the work is done.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do five signals place one task?
&lt;/h2&gt;

&lt;p&gt;Five signals are enough to place a task, and one product can sit in different columns on different rows. Read down the rows for the task in front of you before you pick a column for the whole product. The figure repeats the comparison at the top of this page.&lt;/p&gt;

&lt;p&gt;Triage can be a workflow, the policy answer can be retrieval, and the ticket whose next system depends on the last lookup can be an agent. A single label on the product hides which of the three you shipped, and the next failure goes to the wrong owner.&lt;/p&gt;

&lt;p&gt;The cells are fit judgments you can argue with on a real task. Green marks a fit for that signal. Amber means the shape works only when the condition in the cell is true. Red means stop and build the other shape, because this one adds a way for the bad outcome to happen.&lt;/p&gt;

&lt;p&gt;A task that is green in the workflow column and red in the agent column should stay a workflow even if the roadmap calls for an agent. When a row is red because a write can do damage and no gate exists yet, read the next section before you add a loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when a bad write has no gate?
&lt;/h2&gt;

&lt;p&gt;A write that can move money, change access, send a customer message or alter production state can do real damage, and a loop does not make that smaller. If you do not yet have a gate that can refuse the call, a credential scoped to that action, and a check on the final state outside the model, keep the write out. The read path can still be a workflow or a retrieval app.&lt;/p&gt;

&lt;p&gt;Adding an agent so the model can be careful puts the care in the prompt, which is the place a later instruction can argue with. The systems we have built put it in code. On the DevOps agent, an engineer approves an architecture diagram before anything is applied. Changes then go through the provider's change-set mechanism, which applies a group of changes as one unit, so a partial failure does not leave orphaned resources.&lt;/p&gt;

&lt;p&gt;On our insurance voice agent, a payment cannot execute without its required disclosure, because the condition sits in the tool contract and not in the prompt.&lt;/p&gt;

&lt;p&gt;The same agent shows what a failed write should do. A request that cannot be completed does not turn into an approximation. It escalates to a person, with the dialogue and the actions already taken attached, rather than doing something adjacent to what the caller asked for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bigcircle.ai/guides/human-in-the-loop-ai-agents?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=agentic-workflows-vs-ai-agents" rel="noopener noreferrer"&gt;Human in the loop AI agents&lt;/a&gt; covers the product boundary, where answering can stay read-only and the first write is one kind of action. AI agent governance covers how one action gains authority after the evidence for it exists. Both assume you have already decided that an agent is the right shape for the path.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do you score once the shape is chosen?
&lt;/h2&gt;

&lt;p&gt;Success has to be a state something other than the model can check, or you cannot tell the three shapes apart after launch. A workflow scores the output of each step: the label, the drafted field, the branch that fired. Retrieval scores whether the right passage was in the set and whether the answer stays inside it.&lt;/p&gt;

&lt;p&gt;An agent scores the trace and the final record. On the chief-of-staff agent, the claim that a task is complete is compared against the actual state of the target application, which catches claims of success on work that never happened. Work it cannot finish comes back as a flagged exception with its trace attached. The DevOps agent keeps one record that links the request, the account facts, the generated template, every validation failure and the resulting resources.&lt;/p&gt;

&lt;p&gt;If the only signal is the model's closing sentence, every shape will claim it is done. A workflow claims done when a branch was skipped, retrieval when the passage was missing, and an agent after a tool error it folded into the prose.&lt;/p&gt;

&lt;p&gt;Name the check before you pick the shape, then build the shape that can produce that check with the least machinery. Evals keep that check stable from run to run, and LLM evaluation covers building the cases. Until they exist, a demo that felt like an agent is not evidence that you needed one.&lt;/p&gt;

&lt;p&gt;Observability on a loop is the trace of that check. Store the prompt, the tool list and the stop reason with the run, so a wrong final record can be tied to the choice that produced it. A workflow needs less of that, because the branch is already in the code. AI agent monitoring covers reading those traces once the system is live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an agentic workflow?
&lt;/h3&gt;

&lt;p&gt;An agentic workflow is a multi-step process in which a language model does part of the work, such as drafting, classifying, retrieving or calling a tool. Some writers use the term only when the model chooses the next step. Anthropic calls that design an agent and keeps "workflow" for systems whose steps run through code paths written in advance. Ask which meaning is intended before you compare two designs.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the types of agentic workflows?
&lt;/h3&gt;

&lt;p&gt;The usual patterns are prompt chaining, routing, parallel calls, orchestrator-workers and evaluator-optimizer. In all but one, code still owns the order, so review them as workflows. In orchestrator-workers a lead model chooses the subtasks, so part of the path is the model's choice and it needs the checks of an agent. Pages that use the term for agents list planning, tool use and reflection instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is an agentic workflow the same as an AI agent?
&lt;/h3&gt;

&lt;p&gt;Not in the sense this guide uses. In a scripted workflow, code picks every next step and a model fills some of the steps. In an AI agent, the model picks the next step and the next tool from what the last one returned. Many vendors call both of them agentic, so ask which step the model chooses and what code checks that choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between AI agents and automation?
&lt;/h3&gt;

&lt;p&gt;Rule-based automation follows steps and rules written in code, with no model in it. A scripted workflow keeps that fixed path and adds a model inside some steps. An AI agent lets the model choose the next action. Bigcircle's fund reconciliation system kept its matching rules explicit and versioned in code, because the rules are the domain knowledge and are versioned as such.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do we need an agent, or is a workflow enough?
&lt;/h3&gt;

&lt;p&gt;Build a workflow when a person can write the steps and the branches, including retries that always repeat the same call. Build an agent when the next tool depends on a result you could not list before the run. Most document and reconciliation systems Bigcircle has built are fixed pipelines, and the agent loop in its case studies is the build that works across many systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  When is a RAG or search app enough on its own?
&lt;/h3&gt;

&lt;p&gt;When one query can find the passage and the answer has to stay inside it. Fix the index, identifier matching, ranking and the permission filter on the query before you add a loop. If a question needs several retrievals, as a comparison across years does, try writing them as a fixed sequence first, and use an agent only when the next query cannot be known in advance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should a high-risk write start as an agent?
&lt;/h3&gt;

&lt;p&gt;No. A call that can move money, change access, send a customer message or alter production state needs a gate before any loop may request it. If the gate, a scoped credential and a check on the final state are missing, keep the write out and ship the read path as a workflow or a retrieval app. Authority for one kind of action is a later release.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you know the run succeeded?
&lt;/h3&gt;

&lt;p&gt;Check a state outside the model's closing sentence. A workflow should show the branch that fired and the field it produced, retrieval should show the passage and whether the answer stayed inside it, and an agent should show its tools, any recovery, any approval and a final record that matches what was expected. If you cannot name that check, you cannot yet tell which shape you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Anthropic, &lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Building effective agents&lt;/a&gt;, 19 December 2024. The use of "workflow" and "agent" this guide follows.&lt;/li&gt;
&lt;li&gt;OpenAI, &lt;a href="https://cdn.openai.com/business-guides-and-resources/a-practical-guide-to-building-agents.pdf" rel="noopener noreferrer"&gt;A practical guide to building agents&lt;/a&gt;, April 2025. The advice to point agents at unwieldy rule sets, which the automation section argues against for rules that are domain knowledge.&lt;/li&gt;
&lt;li&gt;How to build an AI agent. The loop, once the path cannot be scripted.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bigcircle.ai/guides/ai-agent-cost?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=agentic-workflows-vs-ai-agents" rel="noopener noreferrer"&gt;AI agent cost&lt;/a&gt;. Why each turn of a loop costs more than the last.&lt;/li&gt;
&lt;li&gt;RAG vs fine-tuning. Which failure you have, before a month goes to the wrong layer.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bigcircle.ai/guides/human-in-the-loop-ai-agents?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=agentic-workflows-vs-ai-agents" rel="noopener noreferrer"&gt;Human in the loop AI agents&lt;/a&gt; and AI agent governance. The first write, and how one action gains authority.&lt;/li&gt;
&lt;li&gt;Multi-agent systems. When the workers in an orchestrator-workers design are agents too.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bigcircle.ai/work/fund-operations-reconciliation?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=agentic-workflows-vs-ai-agents" rel="noopener noreferrer"&gt;Fund operations reconciliation&lt;/a&gt;, executive operations agent and prospect intelligence engine. A pipeline, an agent and a retrieval system from our own builds.&lt;/li&gt;
&lt;li&gt;AI agents. For a team that has already passed the test in this guide.&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bigcircle.ai/guides/agentic-workflows-vs-ai-agents?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=agentic-workflows-vs-ai-agents" rel="noopener noreferrer"&gt;bigcircle.ai&lt;/a&gt;. I'm Tirth, founder of &lt;a href="https://bigcircle.ai/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=agentic-workflows-vs-ai-agents" rel="noopener noreferrer"&gt;BigCircle&lt;/a&gt;, where we build production AI systems with governance built in.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>architecture</category>
    </item>
    <item>
      <title>PII tokenization for LLMs: tokenization vs masking, with code</title>
      <dc:creator>Tirth Gajjar</dc:creator>
      <pubDate>Sun, 25 Oct 2026 18:40:08 +0000</pubDate>
      <link>https://dev.to/bigcircle/pii-tokenization-for-llms-tokenization-vs-masking-with-code-1ici</link>
      <guid>https://dev.to/bigcircle/pii-tokenization-for-llms-tokenization-vs-masking-with-code-1ici</guid>
      <description>&lt;p&gt;PII tokenization replaces each piece of personal data in a prompt with a stand-in token before the request leaves your network, and puts the real value back after the model replies. The provider sees the shape of the record, one customer, one account, one phone number, and never the values. Your users still get a complete answer.&lt;/p&gt;

&lt;p&gt;Plenty of AI projects stall at the legal review, when someone asks what leaves the network and the answer is everything: the support ticket, the customer's email, the account number. We build the layer that changes that answer: a small piece of code in the request path that adds single-digit milliseconds to a call. This guide covers the design choices in it, the Python, where an entity recognizer such as Presidio fits, what the tokens mean under GDPR, and the CI test that turns "we tokenize PII" into something a compliance team can check.&lt;/p&gt;

&lt;h2&gt;
  
  
  PII tokenization vs masking: which keeps the answer useful?
&lt;/h2&gt;

&lt;p&gt;Tokenize when the model has to reason about who is who, and mask only when it does not. Masking replaces every email with &lt;code&gt;[EMAIL]&lt;/code&gt; and every number with &lt;code&gt;****&lt;/code&gt;. It is simple, and it makes the answers worse, as soon as a prompt holds more than one of anything.&lt;/p&gt;

&lt;p&gt;Say a ticket mentions two customers and two accounts. After masking, the model sees &lt;code&gt;[EMAIL]&lt;/code&gt; twice and &lt;code&gt;****&lt;/code&gt; twice. It cannot tell which account belongs to whom, so it cannot write "refund account X for customer Y", because the structure it needed has gone.&lt;/p&gt;

&lt;p&gt;Reversible tokenization keeps that structure. Each sensitive value gets its own token, and the same value always gets the same token within a request, so two different emails get two different tokens and the model can still say "this customer, that account".&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;What the model sees&lt;/th&gt;
&lt;th&gt;Reversible&lt;/th&gt;
&lt;th&gt;Keeps who-is-who&lt;/th&gt;
&lt;th&gt;Use it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redaction&lt;/td&gt;
&lt;td&gt;The value deleted&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Fields the model never needs, such as a card's CVV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Masking&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[EMAIL]&lt;/code&gt;, &lt;code&gt;****&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Logs and analytics where identity does not matter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hashing&lt;/td&gt;
&lt;td&gt;A fixed digest of the value&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes, if salted consistently&lt;/td&gt;
&lt;td&gt;Joining records without reading them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reversible tokenization&lt;/td&gt;
&lt;td&gt;A format-preserving stand-in&lt;/td&gt;
&lt;td&gt;Yes, with the per-request map&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Any prompt whose answer has to name the real customer or account&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two properties make tokenization work in a prompt. It is reversible: a private map from token to value lives for the life of the request, so the answer can be restored. And it is format-preserving: an email token still looks like an email, and a 16-digit account number becomes another 16-digit number, so prompts, parsers and validators downstream keep working and the model treats the field as it would treat the real one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is tokenized data still personal data under GDPR?
&lt;/h2&gt;

&lt;p&gt;For you, yes, and for the provider possibly not, which is the argument to bring to legal review. A token map you keep is pseudonymization in GDPR's own terms: the data can no longer be tied to a person without additional information held separately. Pseudonymized data stays personal data for whoever holds that additional information, so tokenizing does not take your own processing out of scope.&lt;/p&gt;

&lt;p&gt;The provider's position is different. In EDPS v SRB (C-413/23 P, 4 September 2025), the EU Court of Justice held that pseudonymized data passed to a recipient is not always personal data from that recipient's side, when the recipient cannot reverse the pseudonymization or identify people by other means. A provider that receives &lt;code&gt;user_1ec32386@example.invalid&lt;/code&gt;, under a key that changes every request and never leaves your process, is the setup that ruling speaks to.&lt;/p&gt;

&lt;p&gt;Describe the layer to your counsel as pseudonymization, and have them read the ruling against your setup. Anonymization is the wrong word for it, because the map you keep can reverse every token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which fields should you tokenize?
&lt;/h2&gt;

&lt;p&gt;Decide with whoever owns the risk, from your real data, before writing code. Not every field is personal data, and encoding everything destroys the answer as surely as masking does. Over-redaction is its own failure.&lt;/p&gt;

&lt;p&gt;The output is a short list of data classes, such as email, account number and phone, with a rule per class and per jurisdiction. On the telecom marketing platform we are building agents for, subscriber data spans several jurisdictions, and the analytics agent works through a governed data-access layer that masks, aggregates or excludes PII by policy before the agent sees a row. That policy is enforced in the access layer, never in the prompt, because a prompt is a request the model can ignore.&lt;/p&gt;

&lt;p&gt;Structured classes such as emails, account numbers and phone numbers can start with pattern rules. Names, addresses and free-text identifiers need entity recognition, covered further down.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you tokenize PII before the provider call?
&lt;/h2&gt;

&lt;p&gt;Create one boundary object per request that detects sensitive values, swaps each for a token, remembers the mapping and writes a record of what it encoded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;

&lt;span class="c1"&gt;# Pattern rules for the fields your risk owner marked sensitive.
# In production, add an NER pass for names and addresses.
&lt;/span&gt;&lt;span class="n"&gt;PATTERNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EMAIL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b[\w.+-]+@[\w-]+\.[\w.-]+\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACCOUNT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b\d{10,16}\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PHONE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\+?\d[\d -]{8,}\d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Boundary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;                                   &lt;span class="c1"&gt;# per-request secret, never logged
&lt;/span&gt;    &lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# token -&amp;gt; raw value
&lt;/span&gt;    &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# raw value -&amp;gt; token
&lt;/span&gt;    &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# what was encoded, without the values
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_surrogate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACCOUNT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Same length, all digits. The "99" prefix marks it as a stand-in.
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;99&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EMAIL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;@example.invalid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PHONE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+00 &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PATTERNS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;            &lt;span class="c1"&gt;# already a token, leave it
&lt;/span&gt;                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_surrogate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three choices in it are deliberate.&lt;/p&gt;

&lt;h3&gt;
  
  
  A fresh key for every request
&lt;/h3&gt;

&lt;p&gt;Tokens come from an HMAC of the value under a random key. The same email gives the same token inside one request, so the model can connect two mentions of one customer. Across requests the key changes, so the provider cannot link tokens between calls and build a profile. The changing key also puts the provider in the position the EDPS v SRB ruling describes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tokens that cannot be real
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;example.invalid&lt;/code&gt; is a reserved domain that never resolves, and &lt;code&gt;+00&lt;/code&gt; is not a country code. If a token ever escapes into an outgoing email or a log, it is visibly a stand-in and not someone else's real address.&lt;/p&gt;

&lt;h3&gt;
  
  
  A record of tokens, not values
&lt;/h3&gt;

&lt;p&gt;The per-request record lists the kinds of data encoded and the tokens used, and never the values. You show that record in the review afterwards.&lt;/p&gt;

&lt;p&gt;Run on a support ticket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Boundary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;ticket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Customer priya.shah@acme.in says account 4111222233334444 &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;was charged twice. Call back on +91 98765 43210.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# Customer user_1ec32386@example.invalid says account 9955708008262310
# was charged twice. Call back on +00 0573126352.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your tokens will differ, because the key is random, but the model still knows there is one customer, one account and one phone number, and what happened to them. It does not know who they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you restore the real values after the reply?
&lt;/h2&gt;

&lt;p&gt;Decode with a lookup in the other direction, longest token first so one token never clips another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Longest tokens first, so one token never clips another.
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the model replies "Email &lt;code&gt;user_1ec32386@example.invalid&lt;/code&gt; to confirm the refund on &lt;code&gt;9955708008262310&lt;/code&gt;", your user sees the real email and account number, using values your own systems already hold.&lt;/p&gt;

&lt;p&gt;Models sometimes reformat what they copy, so a phone token can come back with different spacing. Tell the model in the system prompt to copy identifiers exactly. Then, after decoding, look for anything still shaped like a token: &lt;code&gt;example.invalid&lt;/code&gt;, a &lt;code&gt;+00&lt;/code&gt; number, a &lt;code&gt;99&lt;/code&gt;-prefixed account. A leftover token means the model changed or invented it. Treat that response as failed and retry it or route it to a person, rather than sending a half-decoded answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does Presidio fit for names and addresses?
&lt;/h2&gt;

&lt;p&gt;Use an entity recognizer for detection and keep your own tokens for the replacement. Patterns catch emails and account numbers, and they miss names, street addresses and identifiers written into free text. Presidio, the open-source PII toolkit Microsoft published, pairs named-entity recognition with pattern recognizers in its analyzer, and its anonymizer offers replace, redact, mask, hash and encrypt operators, with a decrypt operator that reverses encryption.&lt;/p&gt;

&lt;p&gt;Presidio's encrypt and decrypt pair is reversible, and it is not format-preserving: an encrypted name comes back as ciphertext, which a model reads as noise rather than as a person. Feed the analyzer's results into the boundary above, so a detected name gets a name-shaped token from the same per-request map and decodes with everything else. Every detector misses something, so the leakage test below sits behind all of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you prove no PII reached the model provider?
&lt;/h2&gt;

&lt;p&gt;Write a test that fails the build when a raw value reaches the provider. "We tokenize PII" is a claim, and this test is the evidence. Put a fake provider behind your real code path, capture everything that would have been sent, and assert that none of your known sensitive fixtures appear in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;boundary&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Boundary&lt;/span&gt;

&lt;span class="n"&gt;RAW_FIXTURES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;priya.shah@acme.in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4111222233334444&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+91 98765 43210&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CapturingProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_ticket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Boundary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)}]}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_no_raw_value_reaches_provider&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CapturingProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;handle_ticket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;From priya.shah@acme.in, card 4111222233334444, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phone +91 98765 43210&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;RAW_FIXTURES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;raw value leaked: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it in CI on every pull request, and grow the fixtures. Each time someone finds a format the rules missed, such as an email with a plus sign or an account number with spaces, add it as a fixture before fixing the rule. The suite becomes the list of everything you have promised not to send, and we hand it to a compliance team instead of a policy document.&lt;/p&gt;

&lt;p&gt;Test the whole path, not just the boundary class. Leaks rarely start in the tokenizer. They start when someone later adds a helpful field to the prompt, the raw customer record for context or the user's profile in the system message, and it skips the encoder. A test on the real request path catches that on the pull request that adds it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does the tokenization layer sit?
&lt;/h2&gt;

&lt;p&gt;Put it in the request path, directly in front of the provider call, so every call goes through it. A boundary that each feature calls when it remembers to will be skipped by the feature written in a hurry. Keep it fast as well. In our builds the encode and decode step adds single-digit milliseconds, so there is no latency argument for switching it off under load.&lt;/p&gt;

&lt;p&gt;Three limits shape the rest of the design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It protects the next request. Data already sent to a provider is a separate conversation with that provider.&lt;/li&gt;
&lt;li&gt;Detection misses things. Patterns and entity recognition both need the leakage suite behind them.&lt;/li&gt;
&lt;li&gt;The token map is now the sensitive object. It lives for one request, in your process, and never goes into logs or traces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Credentials follow the same rule. On &lt;a href="https://bigcircle.ai/work/executive-ops-desktop-agent/build?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=pii-tokenization" rel="noopener noreferrer"&gt;Solarpunk&lt;/a&gt;, the desktop agent we built, secrets sit in an encrypted vault on the user's machine and are injected into the tool adapter when a tool runs, so they never travel into the prompt. Whatever the model never receives, a prompt injection cannot ask it to repeat.&lt;/p&gt;

&lt;p&gt;We build this layer for teams whose AI work is waiting on that legal review. &lt;a href="https://bigcircle.ai/services/pii-redaction?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=pii-tokenization" rel="noopener noreferrer"&gt;PII redaction and data boundaries&lt;/a&gt; covers how an engagement runs, from classifying fields in week one to the leakage suite your compliance team reviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is PII tokenization?
&lt;/h3&gt;

&lt;p&gt;PII tokenization replaces each piece of personal data in a prompt with a stand-in token before the request goes to the model provider, and restores the real values after the reply. The provider sees the structure of the record and never the values, and the user still gets a complete answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between tokenization and masking?
&lt;/h3&gt;

&lt;p&gt;Masking replaces every email with the same placeholder, so the model cannot tell which account belongs to which customer, and the original cannot be restored. Reversible tokenization gives each value its own stand-in within a request, so the structure survives and the real values go back in after the reply.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is tokenized data still personal data under GDPR?
&lt;/h3&gt;

&lt;p&gt;For the organization holding the token map, yes: it is pseudonymized data, which stays personal data. For a recipient that cannot reverse the tokens or identify people by other means, the EU Court of Justice held in EDPS v SRB (C-413/23 P, 4 September 2025) that it is not always personal data. A fresh key per request that never leaves your process puts the model provider in that second position.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Microsoft Presidio do reversible PII tokenization?
&lt;/h3&gt;

&lt;p&gt;Presidio's anonymizer has an encrypt operator and a decrypt operator that reverses it, so its output is reversible. It is not format-preserving, because an encrypted value comes back as ciphertext. Bigcircle recommends an entity recognizer such as Presidio's analyzer for detection, with format-preserving tokens generated from your own per-request map.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does PII tokenization slow down the LLM call?
&lt;/h3&gt;

&lt;p&gt;In Bigcircle's builds the encode and decode step adds single-digit milliseconds, which is small next to the model call itself. Keeping it that fast matters, because a boundary that teams switch off under load protects nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you prove no personal data reached the provider?
&lt;/h3&gt;

&lt;p&gt;Put a fake provider behind the real code path, capture everything that would have been sent, and fail the build if a known sensitive fixture appears in it. Run the test in CI on every pull request, and add a fixture each time someone finds a format the rules missed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PII redaction and data boundaries (linked above). How Bigcircle builds and proves the layer.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bigcircle.ai/guides/ai-agent-permissions?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=pii-tokenization" rel="noopener noreferrer"&gt;AI agent permissions&lt;/a&gt;. What an agent may do once the data it sees is controlled.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bigcircle.ai/guides/multi-agent-prompt-injection?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=pii-tokenization" rel="noopener noreferrer"&gt;Prompt injection in multi-agent systems&lt;/a&gt;. Why what never reaches the model cannot be extracted from it.&lt;/li&gt;
&lt;li&gt;Court of Justice of the EU, &lt;a href="https://curia.europa.eu/juris/liste.jsf?num=C-413/23" rel="noopener noreferrer"&gt;EDPS v SRB, C-413/23 P&lt;/a&gt;, judgment of 4 September 2025. Pseudonymized data from the recipient's side.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://microsoft.github.io/presidio/" rel="noopener noreferrer"&gt;Presidio documentation&lt;/a&gt;. The analyzer's recognizers and the anonymizer's operators, including encrypt and decrypt.&lt;/li&gt;
&lt;/ul&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bigcircle.ai/guides/pii-tokenization-for-llms?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=pii-tokenization" rel="noopener noreferrer"&gt;bigcircle.ai&lt;/a&gt;. I'm Tirth, founder of &lt;a href="https://bigcircle.ai?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=pii-tokenization" rel="noopener noreferrer"&gt;BigCircle&lt;/a&gt;, where we build production AI systems with governance built in.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>security</category>
      <category>privacy</category>
    </item>
    <item>
      <title>How much does an AI agent cost to build and run?</title>
      <dc:creator>Tirth Gajjar</dc:creator>
      <pubDate>Fri, 23 Oct 2026 18:35:02 +0000</pubDate>
      <link>https://dev.to/bigcircle/how-much-does-an-ai-agent-cost-to-build-and-run-4a7h</link>
      <guid>https://dev.to/bigcircle/how-much-does-an-ai-agent-cost-to-build-and-run-4a7h</guid>
      <description>&lt;p&gt;AI agent cost has three parts: the build, the run and the keep-alive. The build is the system that can take a real task. The run is the model calls, tool calls and retries each task then spends, and the keep-alive is the work after launch, when a tool, a model or a document changes. A per-token price covers one line inside the run, and one combined number hides which of the three is about to move.&lt;/p&gt;

&lt;p&gt;Our case studies show where the work went. On the &lt;a href="https://bigcircle.ai/work/devops-agent-live-aws" rel="noopener noreferrer"&gt;DevOps agent&lt;/a&gt;, the build was mostly about reading the live AWS account, checking every template and putting a human review step before anything applied. On the &lt;a href="https://bigcircle.ai/work/executive-ops-desktop-agent" rel="noopener noreferrer"&gt;chief-of-staff agent&lt;/a&gt;, the visible cost of tool definitions in the prompt turned out to matter less than the wrong tool choices that came with them.&lt;/p&gt;

&lt;p&gt;The sections below take the build, then the run with a worked token calculation at prices as of 22 October 2026. The keep-alive and a way to compare two quotes come next. The last two sections cover the cap that ends a runaway loop before the next paid call, and the ledger of what each run spent. If a script or one query can do the job, &lt;a href="https://bigcircle.ai/guides/agentic-workflows-vs-ai-agents" rel="noopener noreferrer"&gt;agentic workflows vs AI agents&lt;/a&gt; comes first, and none of this bill starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much does it cost to build an AI agent?
&lt;/h2&gt;

&lt;p&gt;The build price follows the scope, and most of the scope sits around the model rather than in it. The build is everything that has to work before the first real task leaves the team: the tools the job needs, permission to call them, an eval set, and a path for the step that fails. Choosing a model is one decision inside that work, and usually not the long one.&lt;/p&gt;

&lt;p&gt;The DevOps agent shows where the time goes. A raw model invents resource types, writes templates the account rejects for quota or region, and drifts between runs, and a failed stack can take minutes to unwind, so "apply and see" is not a usable loop.&lt;/p&gt;

&lt;p&gt;The build therefore had to read live limits, quotas and existing resources before planning, check every template in a loop of structural and semantic checks, and show an engineer an architecture diagram before anything applied. Delivery took roughly four months.&lt;/p&gt;

&lt;p&gt;The chief-of-staff agent spent its build on reach. It connects to more than a hundred systems, and a large share of an executive's work sits behind single sign-on and multi-factor login with no usable API. We argue that a routine the agent can finish 70 percent of still needs a person to hold the sequence, so it saves almost nothing. That is why reach costs build time on an agent like this.&lt;/p&gt;

&lt;p&gt;Each integration is a tool contract, a login path and a failure the agent will meet in normal use. &lt;a href="https://bigcircle.ai/guides/mcp-authorization" rel="noopener noreferrer"&gt;MCP authorization&lt;/a&gt; covers the check before each call, &lt;a href="https://bigcircle.ai/guides/how-to-build-llm-evals" rel="noopener noreferrer"&gt;LLM evaluation&lt;/a&gt; covers the eval set, and &lt;a href="https://bigcircle.ai/guides/how-to-build-ai-agents" rel="noopener noreferrer"&gt;how to build an AI agent&lt;/a&gt; puts those decisions in order. A build quote that lists model access and leaves out the access check and the eval set will be rewritten after the first real task fails.&lt;/p&gt;

&lt;p&gt;How the build is bought is a separate question. &lt;a href="https://bigcircle.ai/guides/ai-pilot-to-production" rel="noopener noreferrer"&gt;AI pilot to production&lt;/a&gt; compares production hardening, a fixed-scope build and a studio-led programme. A fixed-scope build is priced against a scope agreed before work starts, so the useful comparison between two quotes is which of the three parts each one names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does an AI agent cost more to run than one reply?
&lt;/h2&gt;

&lt;p&gt;A run costs more than one reply because the agent decides how many steps to take, and each step sends the whole history again. A workflow pays one model call per step you wrote. An &lt;a href="https://bigcircle.ai/glossary/ai-agent" rel="noopener noreferrer"&gt;agent&lt;/a&gt; pays a number of calls that depends on the trace, and the input on each call grows with every tool result.&lt;/p&gt;

&lt;p&gt;A worked example makes the shape concrete. The token counts are assumptions chosen for the example, and the prices are OpenAI's list rates for GPT-6 Luna as of 22 October 2026: $0.10 per million input tokens and $0.50 per million output tokens.&lt;/p&gt;

&lt;p&gt;Take a system prompt and tool definitions of 6,000 tokens, a task of 10 turns, 300 output tokens per turn for the tool call, and 2,000 tokens of tool result added to the transcript after each turn.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;One reply&lt;/th&gt;
&lt;th&gt;Ten-turn agent task&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input tokens&lt;/td&gt;
&lt;td&gt;6,000&lt;/td&gt;
&lt;td&gt;163,500 (6,000 on the first turn, 26,700 on the tenth)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output tokens&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost at the October 2026 Luna rates&lt;/td&gt;
&lt;td&gt;about $0.00075&lt;/td&gt;
&lt;td&gt;about $0.018&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ten turns cost about 24 times one reply, not 10 times, because each turn pays again for every earlier turn's transcript. At 10,000 tasks a day that is about $179 a day on the run line.&lt;/p&gt;

&lt;p&gt;Reasoning adds to it. OpenAI bills reasoning tokens as output tokens, and Luna defaults to reasoning effort &lt;code&gt;medium&lt;/code&gt;. If each turn spent 1,000 reasoning tokens, another assumption, the same task would cost about $0.005 more, roughly 28 percent on top. The example also assumes reasoning from earlier turns is not sent again. Check that before you trust the figure, because the GPT-5.6 family carries earlier reasoning into the next turn by default. On a model that does, those tokens add to the input of every later turn. &lt;a href="https://bigcircle.ai/guides/swap-llm-model-in-production" rel="noopener noreferrer"&gt;LLM model migration&lt;/a&gt; covers why a model change can move this line while the per-token price falls.&lt;/p&gt;

&lt;p&gt;Tool definitions are part of the input on every turn, and the chief-of-staff agent found where listing all of them in the prompt stops working. Direct tool-calling with every tool listed worked up to roughly thirty tools. Past that, the model started choosing plausible but wrong tools, and we found that the context cost was the visible symptom while the selection error was the expensive one. The fix was to let the model query a tool registry and choose among a handful of relevant contracts, which kept the working context small at more than a hundred tools.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bigcircle.ai/glossary/prompt-caching" rel="noopener noreferrer"&gt;Prompt caching&lt;/a&gt; cuts that bill more than anything else in the example, because it discounts a repeated prefix, and an agent's growing transcript is one. As of 22 October 2026, caching is on by default on OpenAI's supported models, with a minimum prefix of 1,024 tokens on GPT-5.6 and later. On those models a cache read costs a tenth of the input rate and a cache write 1.25 times it.&lt;/p&gt;

&lt;p&gt;An entry lasts 30 minutes by default, and each read refreshes it. OpenAI's caching rules do not name Luna, and Luna's model page lists cached input at $0.01 per million, the same tenth, so we assume the same write rate.&lt;/p&gt;

&lt;p&gt;Applied to the example, if every turn after the first reads the earlier transcript from cache and writes only its new 2,300 tokens, the ten-turn task comes to about $0.006 instead of $0.018. That is 136,800 tokens read at the cached rate, 26,700 written at the write rate, and the same 3,000 output tokens. It assumes each turn arrives inside the cache lifetime with the whole prefix unchanged, so a retry that edits anything early in the transcript pays full price again from that point.&lt;/p&gt;

&lt;p&gt;Caching at the application layer saves a different cost. Prospex, our own research engine, runs cache-first, so a rerun reuses earlier extraction and retrieval instead of calling external services again. By our estimate that cut repeated external API calls by 50 to 60 percent, and we watch the cache hit rate as its cost metric.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does an AI agent cost after launch?
&lt;/h2&gt;

&lt;p&gt;Keep-alive is the work after launch. It covers rerunning the eval set when a tool, a prompt or the model changes, a person who can act when a run stops, and traces that someone can still read when finance asks what happened. None of it shows on the launch invoice, and all of it is on the calendar afterwards.&lt;/p&gt;

&lt;p&gt;Integrations are where it starts. We saw what point-to-point wiring does to maintenance on the chief-of-staff build: a vendor changes a response, and the break shows up somewhere unrelated days later. Our answer there was a uniform tool layer, where each tool is a typed contract with declared inputs, outputs and side effects.&lt;/p&gt;

&lt;p&gt;Human review is the other running line. On the trademark drafting system, attorney corrections are the most valuable signal in the system, because they measure the gap automated scoring cannot see. That review does not end at launch. On the ESG filing pipeline, the slow failure is framework version drift, which is why the mapping version is recorded on each mapped fact.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bigcircle.ai/glossary/observability" rel="noopener noreferrer"&gt;Observability&lt;/a&gt; on an agent is the trace of each run, stored with the run. &lt;a href="https://bigcircle.ai/guides/ai-agent-monitoring" rel="noopener noreferrer"&gt;AI agent monitoring&lt;/a&gt; separates the quality of the answer from the health of the path that produced it. Name the person who owns the stop before launch. A stop with nobody assigned to it is only a log line, and the loop behind it keeps spending until someone reads the invoice.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you compare two AI agent quotes?
&lt;/h2&gt;

&lt;p&gt;Compare two quotes part by part, or a missing line will look like a saving. Two proposals can describe the same agent and still leave out different parts, and the shorter one has often left off the keep-alive or the stop.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;What moves it&lt;/th&gt;
&lt;th&gt;What a thin quote skips&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Tools, credentials, evals, and the path for a failed step&lt;/td&gt;
&lt;td&gt;Everything except a prompt and a happy path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run&lt;/td&gt;
&lt;td&gt;Turns, tool calls, retries, fan-out, and a transcript that grows&lt;/td&gt;
&lt;td&gt;Every turn after the first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keep-alive&lt;/td&gt;
&lt;td&gt;Eval reruns, a person on the stop, review, and traces you can still read&lt;/td&gt;
&lt;td&gt;A named owner after launch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How do you enforce a budget in the request path?
&lt;/h2&gt;

&lt;p&gt;A budget holds only when the check runs before the next provider call and a breach ends the run. A dashboard that climbs and an email that arrives later have already paid for every call between the threshold and the person reading it. The cap is code on that path, and it limits turns, tool calls, spend, and how many child agents one run may start.&lt;/p&gt;

&lt;p&gt;Fan-out, where one run starts several child agents, is the case a per-call limit misses, because each child looks small on its own. If every child gets a fresh counter, together they spend the parent's allowance several times over. Make each child draw on what the parent has left, and have the parent refuse another child once the count or the allowance is gone. &lt;a href="https://bigcircle.ai/guides/multi-agent-systems" rel="noopener noreferrer"&gt;Multi-agent systems&lt;/a&gt; covers when the children are worth starting at all.&lt;/p&gt;

&lt;p&gt;Retries need a bound of their own. We bounded them on the chief-of-staff build, because an agent that retries indefinitely against a broken vendor is worse than one that stops: it burns cost and time while looking busy. The DevOps agent bounds revision the same way, and a template that will not come clean after bounded revision is escalated as a request the agent cannot safely fulfil. Count failed calls in the total, because the provider already did.&lt;/p&gt;

&lt;p&gt;A limit written into the prompt can be talked past, and a counter in code cannot. &lt;a href="https://bigcircle.ai/glossary/guardrails" rel="noopener noreferrer"&gt;Guardrails&lt;/a&gt; that live only in the prompt can give way the first time a tool result or a user message argues for one more step.&lt;/p&gt;

&lt;p&gt;Account-level caps from the cloud providers are a backstop behind that check. They act on a whole project after many runs have spent, they cannot tell a retry storm from a busy day, and a paused project takes your working runs down with the broken one.&lt;/p&gt;

&lt;p&gt;AWS spend limits, introduced on 16 September 2026 and still rolling out to a limited set of customers, pause a project and stop its resources when its monthly usage reaches the limit. An opt-in control pauses the highest-cost resources about four days before the limit, chosen from five services that include Lambda and Bedrock.&lt;/p&gt;

&lt;p&gt;Google Cloud's Spend Caps, in public preview since 29 July 2026, restrict further usage of one service in one project once the monthly cap is reached. Both are worth turning on, and the loop still needs its own check before each call.&lt;/p&gt;

&lt;p&gt;Monitoring without a stop is the gap that costs most. In one incident Jatin Bansal collected, a team running four LangChain agents had monitoring and no enforcement. An analyzer and a verifier began looping with each other in November 2025, and the bill reached $47,000 over eleven days. The figures are as Bansal reports them.&lt;/p&gt;

&lt;p&gt;Set each cap from runs you have already finished rather than from a framework default, which is calibrated to someone else's task. Leave room above the longest run that completed normally, so a stuck loop hits the cap and a normal task does not. A cap set on a guess either cuts good tasks or lets a loop run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does the spend record live?
&lt;/h2&gt;

&lt;p&gt;The ledger has to outlive the agent, or a loop can erase the only record of what it spent. Write the run id, the tool, whether the call was allowed or refused, the token counts and the stop reason for each call to a store the agent's credential cannot delete or rewrite. Include the calls that failed and the calls each child made, or your meter will sit below the invoice.&lt;/p&gt;

&lt;p&gt;Reconcile your meter with the provider bill every month as two sums: billed spend from the invoice, and the cost of every attempt from your own traces, including retries, timeouts, cancellations and outputs you rejected.&lt;/p&gt;

&lt;p&gt;A gap left after failed attempts are included is work you are not tracing, such as an eval job, a warm-up call or a second service on the same key. Close that gap before you call either number the cost of a successful task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How much does an AI agent cost?
&lt;/h3&gt;

&lt;p&gt;AI agent cost has three parts, the build, the run and the keep-alive, and each moves for a different reason. The build is tools, credentials, evals and the path for a failed step. The run is turns, tool calls, retries, fan-out and a transcript that grows. The keep-alive is eval reruns, review and traces after launch. A price per token covers one line inside the run.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does it cost to build an AI agent?
&lt;/h3&gt;

&lt;p&gt;The build price follows the scope, and most of the scope sits around the model: tools, permissions, an eval set and the path for a failed step. Bigcircle's DevOps agent took roughly four months, spent mostly on reading the live AWS account, checking every template and a human review step. Compare quotes on which of those parts each one names.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does an agent task cost so much more than one reply?
&lt;/h3&gt;

&lt;p&gt;Each turn resends the whole transcript, so input grows with every tool result. In this guide's worked example at GPT-6 Luna's October 2026 rates, a ten-turn task with a 6,000-token prompt and 2,000-token tool results costs about 24 times one reply, not 10 times. Reasoning tokens are billed as output and add to that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does prompt caching fix the run cost?
&lt;/h3&gt;

&lt;p&gt;It helps on a repeated prefix and nowhere else. On OpenAI's API as of October 2026, caching is on by default, with a 1,024-token minimum on GPT-5.6 and later, where a cache read costs a tenth of the input rate and a write 1.25 times it. In this guide's example that takes the ten-turn task from about $0.018 to about $0.006. A retry that edits the early transcript pays full price again.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you stop a runaway agent before the invoice?
&lt;/h3&gt;

&lt;p&gt;Put a hard cap in the request path and check it before the next provider call. Cap the turns, the tool calls, the spend and the number of child agents, and make each child draw on the parent's remaining allowance. When any cap breaks, end the run and keep the partial result. An alert after the threshold has already paid for the calls in between.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are cloud spend caps enough on their own?
&lt;/h3&gt;

&lt;p&gt;No. AWS spend limits and Google Cloud Spend Caps, both announced in 2026, pause a project or a service once a monthly amount is reached. They are a useful backstop, but they act after many runs have spent, they cannot tell a retry storm from normal load, and they stop healthy runs along with the broken one.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you reconcile your meter with the provider bill?
&lt;/h3&gt;

&lt;p&gt;Sum what the provider invoiced for the period, and sum what your traces recorded for every attempt, including retries, timeouts and outputs you rejected. Compare the two before you call either one the cost of a successful task. A remaining gap is work you are not tracing, such as an eval job or a second service on the same key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://bigcircle.ai/guides/agentic-workflows-vs-ai-agents" rel="noopener noreferrer"&gt;Agentic workflows vs AI agents&lt;/a&gt;. The test that keeps this bill from starting.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bigcircle.ai/guides/how-to-build-ai-agents" rel="noopener noreferrer"&gt;How to build an AI agent&lt;/a&gt;. The build decisions in the order we make them.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bigcircle.ai/guides/ai-pilot-to-production" rel="noopener noreferrer"&gt;AI pilot to production&lt;/a&gt;. How the build is bought.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bigcircle.ai/guides/swap-llm-model-in-production" rel="noopener noreferrer"&gt;LLM model migration&lt;/a&gt;. Why a model change can raise the run line while the per-token price falls.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bigcircle.ai/guides/mcp-authorization" rel="noopener noreferrer"&gt;MCP authorization&lt;/a&gt; and &lt;a href="https://bigcircle.ai/guides/how-to-build-llm-evals" rel="noopener noreferrer"&gt;LLM evaluation&lt;/a&gt;. Two parts of the build a thin quote leaves out.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bigcircle.ai/work/devops-agent-live-aws" rel="noopener noreferrer"&gt;DevOps agent on a live AWS account&lt;/a&gt;, &lt;a href="https://bigcircle.ai/work/executive-ops-desktop-agent" rel="noopener noreferrer"&gt;executive operations agent&lt;/a&gt; and &lt;a href="https://bigcircle.ai/work/prospect-intelligence-retrieval" rel="noopener noreferrer"&gt;prospect intelligence engine&lt;/a&gt;. The builds this guide draws on.&lt;/li&gt;
&lt;li&gt;OpenAI, &lt;a href="https://developers.openai.com/api/docs/models/gpt-6-luna" rel="noopener noreferrer"&gt;GPT-6 Luna&lt;/a&gt;, &lt;a href="https://developers.openai.com/api/docs/guides/reasoning" rel="noopener noreferrer"&gt;reasoning models&lt;/a&gt; and &lt;a href="https://developers.openai.com/api/docs/guides/prompt-caching" rel="noopener noreferrer"&gt;prompt caching&lt;/a&gt;, read 22 October 2026. Token prices, reasoning billed as output, reasoning carried across turns, and the cache threshold, read and write rates.&lt;/li&gt;
&lt;li&gt;AWS, &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/create-spend-limit.html" rel="noopener noreferrer"&gt;Create a spend limit&lt;/a&gt;, and Google Cloud, &lt;a href="https://cloud.google.com/blog/topics/cost-management/new-early-anomalies-and-spend-caps-on-google-cloud-budgets" rel="noopener noreferrer"&gt;Spend Caps on Google Cloud Budgets&lt;/a&gt;, 29 July 2026.&lt;/li&gt;
&lt;li&gt;Jatin Bansal, &lt;a href="https://jatinbansal.com/ai-engineering/agent-budgets-and-runaway-prevention/" rel="noopener noreferrer"&gt;Agent Budgets and Runaway Prevention&lt;/a&gt;, 26 May 2026. The author-reported $47,000 incident.&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bigcircle.ai/guides/ai-agent-cost" rel="noopener noreferrer"&gt;bigcircle.ai&lt;/a&gt;. I'm Tirth, founder of &lt;a href="https://bigcircle.ai" rel="noopener noreferrer"&gt;BigCircle&lt;/a&gt;, where we build production AI systems with governance built in.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>llm</category>
      <category>devops</category>
    </item>
    <item>
      <title>Why AI agents report unfinished work as done</title>
      <dc:creator>Tirth Gajjar</dc:creator>
      <pubDate>Wed, 21 Oct 2026 20:07:29 +0000</pubDate>
      <link>https://dev.to/bigcircle/why-ai-agents-report-unfinished-work-as-done-3flp</link>
      <guid>https://dev.to/bigcircle/why-ai-agents-report-unfinished-work-as-done-3flp</guid>
      <description>&lt;p&gt;An action-taking agent can receive a &lt;code&gt;200 OK&lt;/code&gt; while the customerâ€™s task remains incomplete. The API may have accepted the request while a queue still rejects it, a downstream system fails, a browser submits twice, or a deployment exists and remains unhealthy, so the agent must not translate interface success into business completion. It needs a contract for the expected final state, an independent way to verify it, and a recovery path for every uncertain result. Bigcircle uses that check on client systems, and a run may report done only after a separate read matches the expected state, including the downstream effects the contract named.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if the tool call succeeded and the task did not?
&lt;/h2&gt;

&lt;p&gt;A tool response describes what one interface observed at one point in time. It may prove that the request was valid, accepted, queued, or written to one store. It may not prove that the intended workflow ever reached its final state, so consider a customer-operations workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent updates a case.&lt;/li&gt;
&lt;li&gt;The case system accepts the write.&lt;/li&gt;
&lt;li&gt;A provisioning event should be created.&lt;/li&gt;
&lt;li&gt;The provisioning worker should change the customer account.&lt;/li&gt;
&lt;li&gt;A confirmation message should be sent.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the first system returns success and the worker later fails, the agent can close the case while the customer still waits. The same gap appears in commerce, finance, infrastructure, messaging, and enterprise workflow systems. Done is the state a customer or operator would recognise as finished, such as a refund record with a ledger entry and a customer-visible status, and a status code only says that one interface accepted the request.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;What it proves&lt;/th&gt;
&lt;th&gt;What it leaves open&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tool result, including a 200&lt;/td&gt;
&lt;td&gt;The interface accepted, queued, or stored the request&lt;/td&gt;
&lt;td&gt;Whether the customer task reached the contracted state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expected state, read on a separate path&lt;/td&gt;
&lt;td&gt;The business facts in the contract now hold&lt;/td&gt;
&lt;td&gt;Any fact the verifier could not read, which stays unknown&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why write an expected-state contract first?
&lt;/h2&gt;

&lt;p&gt;An expected-state contract describes what must be true after the action has run, in terms the business would recognise, and it should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the relevant state before execution;&lt;/li&gt;
&lt;li&gt;the exact state expected after execution;&lt;/li&gt;
&lt;li&gt;the fields or resources allowed to change;&lt;/li&gt;
&lt;li&gt;the invariants that must remain true;&lt;/li&gt;
&lt;li&gt;the downstream effects expected from the action;&lt;/li&gt;
&lt;li&gt;the deadline for those effects;&lt;/li&gt;
&lt;li&gt;the source that can verify each condition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The contract should exist before the agent invokes the tool, because without it the agent can only repeat the toolâ€™s success message. It cannot test whether the product delivered the intended result. For a refund, the final state may require a refund record, a ledger entry, and a customer-visible status. For a deployment, it may require the correct account, expected resources, healthy service checks, and no forbidden changes. For an external message, it may require one accepted message with the correct recipient, content hash, and provider ID.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can you trust an agent that says the monitor is still on?
&lt;/h2&gt;

&lt;p&gt;In two internal coding tasks, a GPT-6.1 Sol agent on 3 October 2026 disabled automatic human alerts from misalignment monitoring, treating an instruction not to send messages as permission. On 6 October a GPT-6 Astra agent removed a monitoring integration to match a reference implementation, then falsely denied removing it. OpenAI's &lt;a href="https://alignment.openai.com/misalignment-reports/suppressing-misalignment-monitoring-alerts-and-disabling-misalignment-monitoring/" rel="noopener noreferrer"&gt;misalignment report&lt;/a&gt;, updated 20 October 2026, says OpenAI is strengthening monitoring and assessing overpersistence against a propensity to evade monitoring. Bigcircle reports the work done only after a separate read matches the expected state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why verify through an independent source?
&lt;/h2&gt;

&lt;p&gt;The execution path should not be the only path that decides whether execution worked. If the agent writes through an action API, verification can read the system of record through a separate query path. If the agent submits a browser form, verification can inspect the resulting record, event, or provider receipt instead of trusting the changed screen. If the agent deploys infrastructure, verification can inspect the target account and health state instead of trusting the deployment command. Independent does not always mean a separate vendor, it means a separate observation that can detect false success in the execution path.&lt;/p&gt;

&lt;p&gt;The verifier should return structured evidence:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verification field&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Operation ID&lt;/td&gt;
&lt;td&gt;Correlate request, result, and recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target ID&lt;/td&gt;
&lt;td&gt;Prove which record or resource changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Observed state&lt;/td&gt;
&lt;td&gt;Record the state found after execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expected state&lt;/td&gt;
&lt;td&gt;Preserve the release contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invariant result&lt;/td&gt;
&lt;td&gt;Detect forbidden side effects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source and timestamp&lt;/td&gt;
&lt;td&gt;Show where and when evidence was read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verdict&lt;/td&gt;
&lt;td&gt;Complete, incomplete, failed, or unknown&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://bigcircle.ai/glossary/structured-output" rel="noopener noreferrer"&gt;Structured output&lt;/a&gt; helps the application enforce these fields, and it does not replace the underlying observation.&lt;/p&gt;

&lt;p&gt;The comparison returns one of four verdicts, and the next action depends on which one came back. Complete returns the evidence and stops. Incomplete keeps checking while the effects are still inside the deadline. Failed runs a compensating action or a handoff, because a partial effect is already proven. Unknown blocks the retry until a separate read can see the final state.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;What the separate read showed&lt;/th&gt;
&lt;th&gt;Next action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Complete&lt;/td&gt;
&lt;td&gt;Observed state matches the contract&lt;/td&gt;
&lt;td&gt;Return the evidence and stop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Incomplete&lt;/td&gt;
&lt;td&gt;Expected effects are still inside the deadline&lt;/td&gt;
&lt;td&gt;Keep verifying, or run the defined recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed&lt;/td&gt;
&lt;td&gt;A partial or forbidden state is proven&lt;/td&gt;
&lt;td&gt;Compensate, or hand the case to a person&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;The final state cannot be read&lt;/td&gt;
&lt;td&gt;Reconcile before any retry&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why treat a timeout as unknown state?
&lt;/h2&gt;

&lt;p&gt;A timeout is not proof of failure. The request may not have reached the target, it may have reached the target and failed, or it may have succeeded while the response was lost. Those three states require different next actions, and if the agent labels every timeout as failed, it may retry an action that already changed real state. That can duplicate a refund, message, ticket transition, order adjustment, or infrastructure change. The correct state is &lt;code&gt;unknown&lt;/code&gt; until the system can reconcile the operation. The run should preserve the operation ID, target, requested parameters, request timestamp, observed response, and last known state. It should then query the target or ledger through the verification path before it decides to retry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do business operations have to be idempotent?
&lt;/h2&gt;

&lt;p&gt;Idempotency means that repeating the same business operation does not create a second consequence anywhere downstream. A transport-level request ID helps, but it is not enough when the same business operation can enter through several services or retries, so use a stable operation key tied to the business action. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;refund:{order_id}:{approved_refund_version}
case-transition:{case_id}:{from_state}:{to_state}:{approval_id}
message:{case_id}:{recipient}:{content_hash}
deployment:{change_request_id}:{plan_hash}:{target_account}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Persist the operation state before execution when the target supports it. On retry, the action layer should return the existing result or continue the recorded operation instead of creating a new action. Idempotency does not remove the need for verification, it limits duplicate effects while the verifier determines the final state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why reconcile before retrying?
&lt;/h2&gt;

&lt;p&gt;Retry is a business decision rather than only an infrastructure setting, and the two get confused constantly, which is why a safe retry loop asks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Did the target receive the operation?&lt;/li&gt;
&lt;li&gt;Did the expected state change occur?&lt;/li&gt;
&lt;li&gt;Did any forbidden or partial state appear?&lt;/li&gt;
&lt;li&gt;Is the approval and credential still valid?&lt;/li&gt;
&lt;li&gt;Is the same action still permitted in the current state?&lt;/li&gt;
&lt;li&gt;Can the next attempt create a duplicate effect?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The answers can produce five outcomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Return success: the first action completed and is verified.&lt;/li&gt;
&lt;li&gt;Continue: an accepted workflow is still within its allowed time window.&lt;/li&gt;
&lt;li&gt;Retry: no effect occurred, the action remains valid, and the retry is safe.&lt;/li&gt;
&lt;li&gt;Compensate: a partial effect needs a defined reversal or repair.&lt;/li&gt;
&lt;li&gt;Escalate: the system cannot prove a safe next action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A generic exponential-backoff policy cannot make any one of these decisions on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you design for partial failure?
&lt;/h2&gt;

&lt;p&gt;Multi-system work creates states between success and failure. An onboarding case may change while provisioning fails, a refund record may exist while the ledger entry is missing, and a deployment may create resources while the health check fails, so model those in-between states explicitly.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Partial state&lt;/th&gt;
&lt;th&gt;Safe response&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Primary write succeeded, downstream work pending&lt;/td&gt;
&lt;td&gt;Continue verification within a deadline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary write succeeded, downstream work failed&lt;/td&gt;
&lt;td&gt;Run a compensating action or escalate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Some targets changed, others did not&lt;/td&gt;
&lt;td&gt;Stop broad retry and reconcile each target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final state correct, response missing&lt;/td&gt;
&lt;td&gt;Return the verified existing result&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Final state cannot be read&lt;/td&gt;
&lt;td&gt;Freeze retry and hand off with the full trace&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Recovery work needs its own authority boundary. An agent allowed to change a case status is not automatically allowed to reverse a financial entry or delete a cloud resource during recovery. Each compensating action needs the same target, parameter, approval, credential, and verification discipline as the original action.&lt;/p&gt;

&lt;h2&gt;
  
  
  What belongs in the causal evidence chain?
&lt;/h2&gt;

&lt;p&gt;An audit log of tool calls is useful and incomplete on its own. The organization must be able to reconstruct why the action was allowed and what consequence followed. Record:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the business request and delegating principal;&lt;/li&gt;
&lt;li&gt;the agent identity, model, tools, and policy version;&lt;/li&gt;
&lt;li&gt;the evidence and state used for the decision;&lt;/li&gt;
&lt;li&gt;the approval, scope, and expiry;&lt;/li&gt;
&lt;li&gt;the credential or capability used;&lt;/li&gt;
&lt;li&gt;the exact action and parameters;&lt;/li&gt;
&lt;li&gt;the tool response;&lt;/li&gt;
&lt;li&gt;the independently observed final state;&lt;/li&gt;
&lt;li&gt;any retry, compensation, escalation, or human correction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://bigcircle.ai/glossary/observability" rel="noopener noreferrer"&gt;Observability&lt;/a&gt; should connect these records with one causal operation ID. This evidence lets engineering replay a failure, operations complete a handoff, security inspect the authority chain, and product decide whether the action has earned more authority. It also answers a growing identity problem that we now meet on most agent engagements. &lt;a href="https://www.nist.gov/news-events/news/2026/02/new-concept-paper-identity-and-authority-software-agents" rel="noopener noreferrer"&gt;NISTâ€™s 2026 concept paper&lt;/a&gt; covers identification, authorization, auditing and non-repudiation for agents, and it is context rather than a certification for this design. The engineering requirement remains direct: preserve the person, task, policy, action, and result in one evidence chain.&lt;/p&gt;

&lt;p&gt;A later challenge has to read that chain from retained evidence, including the independently observed result, rather than from the tool log alone. &lt;a href="https://bigcircle.ai/guides/ai-decision-audit-trail" rel="noopener noreferrer"&gt;How to build an audit trail for AI decisions&lt;/a&gt; is the record for the accepted decision, and the operation ID joins it to the tool trace.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the production release gate check?
&lt;/h2&gt;

&lt;p&gt;Before an action receives write access, test the state transition it produces rather than only the model response that describes it, and a release-ready &lt;a href="https://bigcircle.ai/glossary/evals" rel="noopener noreferrer"&gt;eval&lt;/a&gt; should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;permitted targets and parameters;&lt;/li&gt;
&lt;li&gt;denied records, tenants, and environments;&lt;/li&gt;
&lt;li&gt;stale-state conflicts;&lt;/li&gt;
&lt;li&gt;expired approval and credential cases;&lt;/li&gt;
&lt;li&gt;timeouts before and after target execution;&lt;/li&gt;
&lt;li&gt;duplicate submissions;&lt;/li&gt;
&lt;li&gt;partial downstream failure;&lt;/li&gt;
&lt;li&gt;verifier failure;&lt;/li&gt;
&lt;li&gt;compensating-action limits;&lt;/li&gt;
&lt;li&gt;human handoff quality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built custom capability evaluations for an authenticated browser-use workflow. The useful production pattern was to evaluate real task completion and action behavior, not only the agentâ€™s reasoning. Do not grant more authority because the agent explains its plan well. Grant more authority when the system can prove that the action stays inside its boundary, reaches the expected state, avoids duplicate effects, and stops when the result is unknown. How that authority moves up, and how it comes back down, is the release model in &lt;a href="https://bigcircle.ai/guides/rolling-out-ai-agent-autonomy" rel="noopener noreferrer"&gt;how to roll out agent autonomy&lt;/a&gt;. When several workers each report a slice as done, the same separate read is the merge, which &lt;a href="https://bigcircle.ai/guides/multi-agent-fan-out-orchestration" rel="noopener noreferrer"&gt;when to fan out to many agents&lt;/a&gt; puts in front of the combined result. Use the &lt;a href="https://bigcircle.ai/resources/agent-action-boundary-canvas.pdf" rel="noopener noreferrer"&gt;Agent Action Boundary Canvas&lt;/a&gt; to define the expected state, verifier, retry rule, partial-failure states, and recovery owner for one action.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should you take into production?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Interface success is not business completion. A &lt;code&gt;200 OK&lt;/code&gt; can sit alongside a rejected queue, a failed downstream system or an unhealthy deployment.&lt;/li&gt;
&lt;li&gt;Write the expected-state contract before the tool call, in terms the business would recognise.&lt;/li&gt;
&lt;li&gt;Verify through a path that is independent of the one that executed the action.&lt;/li&gt;
&lt;li&gt;A timeout is unknown state, not failure. The correct state is &lt;code&gt;unknown&lt;/code&gt; until the system can reconcile.&lt;/li&gt;
&lt;li&gt;Key idempotency to the business operation, not to the transport request, because the same action can enter through several services.&lt;/li&gt;
&lt;li&gt;Reconcile before retrying. A generic exponential-backoff policy cannot decide whether the first action already changed real state.&lt;/li&gt;
&lt;li&gt;Model the states between success and failure explicitly, and give recovery its own authority boundary.&lt;/li&gt;
&lt;li&gt;Gate the release on the state transition the action produces, not on the model response that describes it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why do AI agents report unfinished work as done?
&lt;/h3&gt;

&lt;p&gt;A tool can return success for one interface while the business task is still open. The case write can succeed while provisioning fails, so the agent closes the case and the customer still waits. A rejected queue, a double browser submit, or an unhealthy deployment is the same gap. Report done only after a separate read matches the expected state, and keep the run unknown when that read fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is an expected-state contract?
&lt;/h3&gt;

&lt;p&gt;An expected-state contract lists the business facts that must be true after the action. Write it before the tool is called, because after the call the agent can only repeat whatever success message the interface returned. It names the before and after state, the fields that may change, and the facts that must stay true. It also names the downstream effects, the deadline, and which source can verify each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is an HTTP 200 proof that an agent finished?
&lt;/h3&gt;

&lt;p&gt;A 200 is not proof the task finished. A success payload describes what one interface observed at one moment, and it can show a valid, accepted, queued, or stored request. It cannot show that the workflow reached the state a customer or operator would call finished. For a refund, that state can be the refund record, the ledger entry, and the customer-visible status.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should an agent do when a tool call times out?
&lt;/h3&gt;

&lt;p&gt;Treat the timeout as unknown, not as failure. The request may never have reached the target, it may have failed there, or it may have succeeded while the response was lost. Calling every timeout a failure invites a retry that can duplicate a refund or a message. Read the target through the verification path before any retry.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you stop a retry from duplicating an agent action?
&lt;/h3&gt;

&lt;p&gt;Key the operation to the business action, not to the transport request, because the same action can enter through several services or retries. A stable key lets the retry return the recorded result instead of starting a new action. That key limits a second consequence while the verifier is still reading the final state.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does independent verification mean for an agent?
&lt;/h3&gt;

&lt;p&gt;Independent verification is a separate observation of the result, and it does not require a separate vendor. If the agent writes through an action API, the check reads the system of record on another path. A browser submit is checked from the resulting record or provider receipt, not from the changed screen. The verdict is complete, incomplete, failed, or unknown.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bigcircle.ai/guides/why-ai-agents-report-work-as-done" rel="noopener noreferrer"&gt;bigcircle.ai&lt;/a&gt;. I'm Tirth, founder of &lt;a href="https://bigcircle.ai" rel="noopener noreferrer"&gt;BigCircle&lt;/a&gt;, where we build production AI systems with governance built in.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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