<?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: Trida AI</title>
    <description>The latest articles on DEV Community by Trida AI (tridaai).</description>
    <link>https://dev.to/tridaai</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%2F15429%2F13a3f6a4-7cc5-4551-b109-95e2f17a742c.png</url>
      <title>DEV Community: Trida AI</title>
      <link>https://dev.to/tridaai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tridaai"/>
    <language>en</language>
    <item>
      <title>Compaction Is a Lossy Handoff</title>
      <dc:creator>FDE at Trida AI</dc:creator>
      <pubDate>Tue, 10 Nov 2026 12:10:07 +0000</pubDate>
      <link>https://dev.to/tridaai/compaction-is-a-lossy-handoff-1omi</link>
      <guid>https://dev.to/tridaai/compaction-is-a-lossy-handoff-1omi</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://trida.ai/blog/compaction-is-a-lossy-handoff" rel="noopener noreferrer"&gt;Trida AI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every long agent run eventually forgets on purpose. The only real question is whether anyone decided, in advance, what it would forget.&lt;/p&gt;

&lt;p&gt;That scheduled forgetting is compaction. Anthropic's &lt;a href="https://www.anthropic.com/research/effective-context-engineering-for-ai-agents" rel="noopener noreferrer"&gt;context engineering&lt;/a&gt; guidance describes it as taking a conversation that is nearing the context window limit, summarizing its contents, and reinitiating a new window with the summary. In Claude Code, the summary keeps architectural decisions, unresolved bugs and implementation details while discarding redundant tool outputs, and the agent carries on with the compressed context plus the five most recently accessed files.&lt;/p&gt;

&lt;p&gt;Read that example slowly, because the interesting part is the choosing. A summarizer looked at everything the run had seen and decided which of it the future deserved. The run that continues on the other side cannot ask what was cut. It cannot even know that something was cut. It has the summary, and from that moment the summary is the past.&lt;/p&gt;

&lt;p&gt;That is why we treat compaction as a handoff rather than a compression setting. A handoff is a transfer to someone who was not there, and it fails in predictable ways: outcomes arrive without their reasons, exact figures get rounded into adjectives, and an open question comes back sounding settled. Compaction fails in exactly those ways, because underneath the mechanism it is the same act.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write the keep list before you need it
&lt;/h2&gt;

&lt;p&gt;You cannot brief the summarizer well at the moment the window fills. By then the context is crowded, the run is mid-task, and whatever instructions you bolt on are competing with the very material they are meant to sort. The keep list belongs in the design of the run, written while everyone is calm.&lt;/p&gt;

&lt;p&gt;Ours is short. The goal, in the words it was given. Decisions, with the reason each one went the way it did. Open questions, still marked open. And exact values (case IDs, amounts, error strings, version numbers) copied character for character, because a summarizer paraphrasing an identifier produces a confident reference to something that does not exist.&lt;/p&gt;

&lt;p&gt;Anthropic's tuning advice runs the same direction: start by maximizing recall, capturing everything that might be relevant, then iterate toward precision by cutting what proves superfluous. Err toward keeping too much on the first pass. You can measure and trim a summary that kept a useless paragraph. You cannot measure a fact that never made it across.&lt;/p&gt;

&lt;p&gt;The provider machinery will not make these choices for you. Claude's &lt;a href="https://platform.claude.com/docs/en/build-with-claude/compaction-threshold" rel="noopener noreferrer"&gt;server-side compaction&lt;/a&gt; fires when input tokens cross a trigger you set, 150,000 by default with a floor of 50,000. &lt;a href="https://developers.openai.com/api/docs/guides/compaction" rel="noopener noreferrer"&gt;OpenAI's compaction&lt;/a&gt; works the same way server-side, and also offers a standalone endpoint that returns a compacted window you pass on as-is. Both answer the question of when. Neither knows what matters in your run.&lt;/p&gt;

&lt;p&gt;Timing still deserves thought. Compact too late and the summarizer is working from a crowded, degraded history, summarizing the noise along with the signal. Compact too often and the losses stack, because the second compaction does not summarize your run. It summarizes a summary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1t9wdybox1ogvkudyj0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu1t9wdybox1ogvkudyj0.webp" alt="What the new window contains, and what stays outside. The full history is kept outside the window and stays fetchable; the summary carries pointers back to it, so a doubted claim can be checked against the original instead of trusted." width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What the new window contains, and what stays outside. The full history is kept outside the window and stays fetchable; the summary carries pointers back to it, so a doubted claim can be checked against the original instead of trusted.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the original within reach
&lt;/h2&gt;

&lt;p&gt;A summary that replaces the only copy of the history is a single point of failure with a fluent writing style. Keep the full transcript and the run's artifacts outside the window, in a store the agent can read, and make the summary carry pointers: which tool result, which document version, which stretch of the log stands behind each kept claim. The pointer is what turns compaction from an act of faith into something auditable.&lt;/p&gt;

&lt;p&gt;Start lighter than compaction where you can. Anthropic calls &lt;a href="https://platform.claude.com/docs/en/build-with-claude/context-editing" rel="noopener noreferrer"&gt;tool result clearing&lt;/a&gt; one of the lightest-touch forms of compaction: remove raw tool outputs once the agent has acted on them. A long test log that produced one failing test name rarely needs to survive verbatim. The failing name and a pointer to the log do. Plenty of runs can clear aggressively for a long time and compact rarely, which is the right order. Deleting what is provably dead is a safe operation. Summarizing what is still alive is a judgment call.&lt;/p&gt;

&lt;h2&gt;
  
  
  One summary, checked against the original
&lt;/h2&gt;

&lt;p&gt;Here is the check we mean, on an example compacted summary from a contract review. The lines are the example summary's own. The notes are what a comparison against the original history found.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fie9o0jp80u2bubstjduf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fie9o0jp80u2bubstjduf.webp" alt="One summary, checked against the original. Example content. Three lines are safe to act on; two need the original opened first — the flattened line, which kept a check but lost which version it was checked against, is the dangerous kind, because it reads as complete." width="799" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One summary, checked against the original. Example content. Three lines are safe to act on; two need the original opened first — the flattened line, which kept a check but lost which version it was checked against, is the dangerous kind, because it reads as complete.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Notice which line does the damage. It is not a missing line. The summary kept the payment-terms check and dropped the fact that the agreement it was checked against had been superseded, so the surviving sentence is accurate and misleading at the same time. Fluent summaries prefer closure. An unresolved dispute tends to come back as a reviewed item, a superseded source as a source. If you sample only one thing after compaction, sample for false closure: open items that returned sounding finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where compaction stops being the answer
&lt;/h2&gt;

&lt;p&gt;Anthropic's work on &lt;a href="https://www.anthropic.com/research/effective-harnesses-for-long-running-agents" rel="noopener noreferrer"&gt;long-running agent harnesses&lt;/a&gt; is blunt on the limit: compaction on its own is not sufficient, because it does not always pass perfectly clear instructions across to the next session. Their answer is to leave structured artifacts outside the window (a progress file, a feature list) that the next session reads directly. When a task's state can live in artifacts like that, a clean restart reading good artifacts can beat a summary trying to hold everything in prose.&lt;/p&gt;

&lt;p&gt;Compaction also cannot rescue a run whose problem was never length. If the agent is looping, summarizing hands it a shorter version of the same wrong history to continue from. If the underlying data is bad, the summary preserves the badness more compactly. And some stretches of work should not be summarized mid-flight at all: where the exact wording is itself the evidence, end the run at a boundary, keep the record whole, and start the next run from artifacts.&lt;/p&gt;

&lt;p&gt;So test it the way you would test anything else that can silently change behaviour. Replay finished runs, compact them at the threshold you plan to use, and diff what the summary claims against what the original shows. A compaction setting that has never been through that check is a hope with a token count attached.&lt;/p&gt;

&lt;p&gt;The design question was never whether to compact. Long runs leave you no choice. It is what your run is allowed to forget, who wrote that list, and whether the original is still there on the day the summary turns out to be wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work with us
&lt;/h2&gt;

&lt;p&gt;If your long-running agents come back from compaction missing the one detail the task turned on, the fix is rarely a bigger window. It is a keep list written in advance, pointers back to the original, and a check on what survived. Tell us about the run through &lt;a href="https://trida.ai/intake" rel="noopener noreferrer"&gt;Trida AI's intake&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tridaai</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>Agent Runs Need Deadlines</title>
      <dc:creator>FDE at Trida AI</dc:creator>
      <pubDate>Mon, 09 Nov 2026 12:09:10 +0000</pubDate>
      <link>https://dev.to/tridaai/agent-runs-need-deadlines-3klo</link>
      <guid>https://dev.to/tridaai/agent-runs-need-deadlines-3klo</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://trida.ai/blog/agent-runs-need-deadlines" rel="noopener noreferrer"&gt;Trida AI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The quote went out at half past nine. The agent that was checking its prices finished at twenty to ten, found two prices changed, and did what it had been asked to do: it updated the customer record and drafted a correction. Nothing had gone wrong, technically. The work was simply late, and late work, acted on, became a second problem on top of the first.&lt;/p&gt;

&lt;p&gt;Take that scene as a pattern rather than a report. It repeats wherever an agent's answer has a moment it is needed by: a summary wanted before a meeting starts, a check wanted before a payment is released, a price wanted before the quote is sent. Many runs carry budgets for steps and tokens. Fewer carry the time by which the answer stops being useful. Those are different limits, and the second one is the one the business actually feels.&lt;/p&gt;

&lt;h2&gt;
  
  
  A timeout is not a deadline
&lt;/h2&gt;

&lt;p&gt;A timeout is a local allowance. This tool call may take 30 seconds, that model call may take a minute, and each allowance starts fresh when the call starts. Ten calls in a row, each inside its own timeout, can keep a run going for as long as the steps hold out. Nothing anywhere in that arrangement knows what time the whole thing was supposed to be done by.&lt;/p&gt;

&lt;p&gt;A deadline is a point in time. The &lt;a href="https://grpc.io/docs/guides/deadlines/" rel="noopener noreferrer"&gt;gRPC deadlines&lt;/a&gt; guide puts it plainly: a deadline is the point past which a client is no longer willing to wait for a response. It does not reset. Every step inside the run shares the same shrinking remainder, and when the remainder reaches zero, the question changes from "how much longer can this take?" to "why is it still running at all?"&lt;/p&gt;

&lt;p&gt;Where the deadline comes from matters. It is not an infrastructure setting, because infrastructure does not know when the quote goes out. It belongs to the task, and it is set when the task is accepted: this answer is worth having until 09:30, after that it is worth much less, and after 10:00 it may be worth less than nothing, because acting on it creates clean-up work. Some tasks get hours. An overnight research run gets until morning. The point is not that the window is short. The point is that there is one, the run knows it, and it was chosen by whoever knows what the answer is for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjmckgqoux6t0xyklrrha.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjmckgqoux6t0xyklrrha.webp" alt="One run, and the clocks nested inside it. Example times. Each step and tool call gets what is left of the run's window, never a fresh allowance of its own — and the last slice is reserved for reporting back, not for starting new work." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One run, and the clocks nested inside it. Example times. Each step and tool call gets what is left of the run's window, never a fresh allowance of its own — and the last slice is reserved for reporting back, not for starting new work.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The remaining time has to travel with the work
&lt;/h2&gt;

&lt;p&gt;A deadline that sits in the orchestrator's configuration and nowhere else will be missed politely, one well-behaved step at a time. It has to travel. When a step calls a tool, it hands over the time remaining, not a new budget. When a run starts a child run, the child inherits the parent's deadline, minus the reserve the parent keeps for reading the result and reporting back. Where gRPC propagates a deadline, it handles the clock problem in the conversion: the deadline becomes a timeout with the elapsed time already deducted, so machines that disagree about the time still agree about how much is left. Agent runtimes need the same habit. Pass durations, not timestamps.&lt;/p&gt;

&lt;p&gt;The check belongs before the work, not after it. Google's SRE book, in &lt;a href="https://sre.google/sre-book/addressing-cascading-failures/" rel="noopener noreferrer"&gt;Addressing Cascading Failures&lt;/a&gt;, describes a request that sat in a queue for eleven seconds against a ten-second deadline: by the time a server picked it up, the caller had given up, and any work done on it earned no credit at all. Their rule is that a server handling a request in stages should check the time left at each stage before doing more work on it. Translated to an agent: before each step, and before each expensive tool call, compare what is left with what the step plausibly needs. If the step cannot finish inside the window, do not start it and call the outcome a timeout later. Decline it now, while declining is still cheap, and spend the remainder closing out.&lt;/p&gt;

&lt;p&gt;The split of where time goes is worth copying too. &lt;a href="https://docs.temporal.io/evaluate/features/timeouts-and-retries" rel="noopener noreferrer"&gt;Temporal's timeouts&lt;/a&gt; separate how long an activity may wait to be picked up from how long it may run once started, because those are different failures with different fixes. A run can miss its deadline in a queue, in an approval inbox, or mid-execution. If the record only says "deadline exceeded," you will fix the wrong one. Log which clock ate the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the clock runs out
&lt;/h2&gt;

&lt;p&gt;At zero, the run stops starting things. That sounds obvious and is widely not what happens: retries fire, a slow source gets one more chance, a final synthesis step begins with ninety seconds of context assembly still ahead of it. A deadline with no change in behaviour at zero is a comment in a config file.&lt;/p&gt;

&lt;p&gt;The harder question is the late result. Sources do not know about your deadline. The supplier portal that was slow at 09:18 may answer at 09:41, correct and complete. The rule should be: a result that arrives after the deadline is recorded as late and is not acted on automatically. The world it was gathered for has moved. The quote went out, the meeting started, the record was written by someone else. Applying the late result without a person looking at it first turns a timing problem into a correctness problem. Logged with its arrival time, it is still useful evidence. Applied silently, it is a surprise.&lt;/p&gt;

&lt;p&gt;What the run owes instead is a partial answer before the deadline, not a complete one after it. When the reserve slice begins, the run closes with what it has: the prices it verified, the one it could not, and the gap stated in plain words. A person can send a quote with one price flagged as unverified. Nobody can use a perfect check that arrives after the quote has gone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp924nhbc17b11fssw61p.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp924nhbc17b11fssw61p.webp" alt="Run log for a pricing check that missed one source. Example entries. The partial answer goes out inside the window with its gap flagged; the slow source's reply is kept as a late record, not applied." width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Run log for a pricing check that missed one source. Example entries. The partial answer goes out inside the window with its gap flagged; the slow source's reply is kept as a late record, not applied.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Deadlines do real damage when they are picked badly. Set one from impatience instead of from the task, and you will train the system to fail expensive requests consistently — the SRE book warns about exactly that failure shape, where some more expensive requests are the ones a short deadline makes fail consistently. Set one that pauses silently whenever a human approval is involved, and you have a loophole, not a deadline: if waiting on a person should move the deadline, move it explicitly, in the record, where someone can see who moved it and by how much. And a genuinely open-ended task should not be given a pretend window to satisfy a policy. Give it the honest one, even if that is tomorrow morning.&lt;/p&gt;

&lt;p&gt;So the test is two questions, asked of any agent run you operate. When does this answer stop being useful? And does the run itself know? If the first answer lives only in a person's head, the second is no, and the run will keep doing correct work at the wrong time — which, from the receiving end, looks exactly like being wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work with us
&lt;/h2&gt;

&lt;p&gt;If your agents are doing sound work that lands after the moment it was needed, the fix is rarely a faster model. It is a deadline drawn from the task, carried through the run, and honoured in the last minutes. Tell us where your runs are landing late through &lt;a href="https://trida.ai/intake" rel="noopener noreferrer"&gt;Trida AI's intake&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tridaai</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>Async Agents Move the Failure Modes</title>
      <dc:creator>FDE at Trida AI</dc:creator>
      <pubDate>Sun, 08 Nov 2026 12:06:19 +0000</pubDate>
      <link>https://dev.to/tridaai/async-agents-move-the-failure-modes-23hg</link>
      <guid>https://dev.to/tridaai/async-agents-move-the-failure-modes-23hg</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://trida.ai/blog/async-agents-move-the-failure-modes" rel="noopener noreferrer"&gt;Trida AI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The most important design decision in the new async agent pattern is not the parallelism. It is who stays behind to answer the phone.&lt;/p&gt;

&lt;p&gt;LangChain's &lt;a href="https://www.langchain.com/blog/agents-are-going-async" rel="noopener noreferrer"&gt;Agents Are Going Async&lt;/a&gt; describes the split its Deep Agents framework now ships: an orchestrator holds the conversation with the user while &lt;a href="https://docs.langchain.com/oss/python/deepagents/async-subagents" rel="noopener noreferrer"&gt;async subagents&lt;/a&gt; run the long work in the background. In the older shape, a subagent call blocked the orchestrator's loop until it returned, so one slow task froze everything, including the conversation. Now the hand-off returns at once, the loop stays free, and the amount of work an agent can carry grows with the number of workers instead of the length of one loop.&lt;/p&gt;

&lt;p&gt;We think the direction is right. We also think teams will misread what it fixes. Async removes a waiting problem. In exchange it creates a supervision problem, because work that used to fail in front of you now fails, waits, and finishes somewhere you are not looking. The failure modes did not leave. They moved.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the orchestrator actually does all day
&lt;/h2&gt;

&lt;p&gt;The mechanics are worth stating plainly, because the duties hide inside them. The orchestrator manages background work through five operations: start a task, check its status, send it new instructions, cancel it, and list everything in flight. Every unfinished task is listed in its system prompt, so it always carries a picture of what is outstanding.&lt;/p&gt;

&lt;p&gt;Steering splits into two choices with different costs. An interrupt (the default) stops the worker's current run and restarts it with the new instruction — right for a correction, wasteful for an addition, because the run in progress is stopped before it finishes. An enqueue holds the follow-up until the current run finishes, which preserves that work and delays the change. Neither is a detail. Picking between them, task by task, is a judgment the orchestrator now makes on your behalf.&lt;/p&gt;

&lt;p&gt;Results find their way back three ways. A busy orchestrator picks up finished results before its next model call. An idle one is woken by the worker reporting in. A paused one — waiting on an approval — is left alone, and the result waits with it. And when a background worker itself needs an approval, the request surfaces on that worker's own thread, where the user answers it without disturbing the main conversation.&lt;/p&gt;

&lt;p&gt;Read that list again as a job description. Track everything outstanding. Decide what an interruption should cost. Hold results until the moment they can be received. Route approvals to the right thread. That is not routing. That is supervision, and it is the part of the system that now decides whether the whole arrangement feels reliable or haunted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb4o71quvi5f4murtw9hc.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb4o71quvi5f4murtw9hc.webp" alt="What each part is doing while the work runs. One vendor comparison, delegated once, still being steered the next morning — the conversation and the work now run on separate clocks." width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What each part is doing while the work runs. One vendor comparison, delegated once, still being steered the next morning — the conversation and the work now run on separate clocks.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the failures go when nobody is watching
&lt;/h2&gt;

&lt;p&gt;A synchronous agent fails in the conversation. The user sees the error, asks a question, redirects. A background worker fails at 2 a.m. in a thread nobody has open. Unless the system gives that failure a destination (a record a person can inspect, with what the task was, how it failed, and what it touched before it stopped), the failure is just missing output. The user finds out when the result they were promised does not arrive, which is the worst notification design there is.&lt;/p&gt;

&lt;p&gt;Waiting rots in the same quiet way. An approval request sitting on a background thread has no natural owner: the user may not know it exists, and the orchestrator is paused around it. A task can wait for days while every dashboard shows the system as healthy, because waiting is not an error state. It needs an age, and at some age it needs to escalate to a person.&lt;/p&gt;

&lt;p&gt;Two more relocations are easier to miss. A worker that runs after the user has logged off runs on access granted inside a conversation that is over, so the grant's lifetime belongs to the task, not to the session. A credential that outlives the work is a standing invitation, whoever ends up holding it. And a result that lands the next morning arrives after the user's context has moved on. It has to carry its receipts with it: what it read, when it read it, and which claims stand on which sources. A bare answer, delivered late, asks to be trusted at exactly the moment checking it is hardest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test is the task list
&lt;/h2&gt;

&lt;p&gt;Here is the evaluation we would apply to any async agent setup, our own included. Show the user a single list of everything running in their name: each task, when it started, what state it is in, what it is waiting on, and what happens if it fails. If that list exists and stays honest, the supervision duties above have somewhere to live. If it does not, and the only way to learn what is outstanding is to ask the orchestrator and take its word, then the background is a black box with good manners.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjkxxwhnwxl1sxxctrv8y.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjkxxwhnwxl1sxxctrv8y.webp" alt="The background task list, as the user sees it. Waiting and failed appear as states with ages and owners, not as gaps between messages." width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The background task list, as the user sees it. Waiting and failed appear as states with ages and owners, not as gaps between messages.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;None of this makes async a bad trade. For genuinely long work (research that takes an hour, a migration that runs overnight) a blocking conversation is the worse design, and we would not go back. But there are places the split earns nothing. Short tasks pay the coordination cost and get nothing in return. Work the user needs to watch unfold, because the reasoning is the product, gets worse when it happens offstage. And tools that cannot report progress or accept a cancellation make poor background workers: they cannot be supervised, only launched.&lt;/p&gt;

&lt;p&gt;So build the orchestrator as a supervisor, not a dispatcher. Give its tasks a ledger, its failures a destination, its approvals an age, and its results receipts. The parallelism is the easy part, and it is mostly solved. What async actually moves into your product is the oldest management problem there is: work you cannot see, done by workers you have to trust, reported back after you have stopped watching. Design for that, and the pattern holds up. Design only for the parallelism, and you will meet the failure modes at their new address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work with us
&lt;/h2&gt;

&lt;p&gt;If your agents are starting to run work in the background (research, migrations, overnight jobs) and nobody can say exactly what is outstanding, what it is waiting on, or where its failures land, we can help you build the supervision layer before the list gets long. Tell us about the deployment through &lt;a href="https://trida.ai/intake" rel="noopener noreferrer"&gt;Trida AI's intake&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tridaai</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>Cancelling an Agent Run Cleanly</title>
      <dc:creator>FDE at Trida AI</dc:creator>
      <pubDate>Sat, 07 Nov 2026 12:04:49 +0000</pubDate>
      <link>https://dev.to/tridaai/cancelling-an-agent-run-cleanly-2fbp</link>
      <guid>https://dev.to/tridaai/cancelling-an-agent-run-cleanly-2fbp</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://trida.ai/blog/cancelling-an-agent-run-cleanly" rel="noopener noreferrer"&gt;Trida AI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Clicking stop is the easy part. Everything after the click is where cancelling an agent run actually happens, and in most products it doesn't. Someone watches a run head in the wrong direction, clicks stop, the spinner disappears, and the run is filed as cancelled. Meanwhile a tool call that was already sent finishes a minute later and writes anyway. The screen said stopped. The world disagrees.&lt;/p&gt;

&lt;p&gt;That gap isn't a UI bug. It's what cancellation defaults to when nobody designed it: a display state. Making stop mean something in the systems the run touched is a design problem, and durable workflow engines have already mapped most of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cancellation is a request the run has to hear
&lt;/h2&gt;

&lt;p&gt;Temporal draws the line clearly in its documentation of &lt;a href="https://docs.temporal.io/encyclopedia/workflow/cancellation-and-termination" rel="noopener noreferrer"&gt;cancellation and termination&lt;/a&gt;. Cancellation is cooperative: the service delivers the request and the workflow code decides what to do with it, so it can release resources, compensate for steps that finished, and, in most SDKs, pick its own closing status. Termination is the opposite. The execution closes immediately, the code never sees the request, and no cleanup runs. Anything the run was holding stays held: Temporal's own list is reserved inventory, held locks, and partial writes left as written.&lt;/p&gt;

&lt;p&gt;Agent runtimes need both words, used honestly. If your stop path kills the process, call it termination and accept what it leaves behind. If you call it cancellation, the run has to receive the request while it can still act on it, which means the signal needs a delivery route to every part of the run: the orchestrator, any child runs it spawned, and the tool call in flight.&lt;/p&gt;

&lt;p&gt;Delivery is the part people assume. Temporal delivers an activity cancellation when &lt;a href="https://docs.temporal.io/develop/java/workflows/cancellation" rel="noopener noreferrer"&gt;the activity heartbeats&lt;/a&gt;: a long-running activity that doesn't heartbeat can't receive the request at all, and runs to completion, failure, or timeout instead. Translate that to agents and the equivalent is a tool wrapper that never checks in. A forty-minute export job with no progress reporting isn't just hard to watch. It's uncancellable, and no button upstream changes that.&lt;/p&gt;

&lt;p&gt;Model calls have their own version of the same limit. OpenAI's API reference says a response can be cancelled only when it was created with the &lt;a href="https://developers.openai.com/api/reference/python/resources/responses/methods/cancel" rel="noopener noreferrer"&gt;background parameter&lt;/a&gt; set to true; a synchronous call has no handle to cancel by. Whether a run can be stopped is often decided at the moment the work starts, not when someone clicks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The call in flight decides what stop means
&lt;/h2&gt;

&lt;p&gt;Picture a run with four steps. Two have finished. The fourth hasn't started, and stopping that one is easy: it simply never runs. Everything hard about cancellation lives in step three, the call that is in flight when the request arrives.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0cgok2zfi2ivskmx5vin.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0cgok2zfi2ivskmx5vin.webp" alt="One run, the moment cancel arrives. The finished steps are history, the unstarted step is easy, and the call in flight is the one that has to report what it actually did." width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One run, the moment cancel arrives. The finished steps are history, the unstarted step is easy, and the call in flight is the one that has to report what it actually did.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That call ends in one of three states, and the runtime has to learn which. It stopped before acting. It acted, and the effect landed. Or it timed out in the middle, and nobody yet knows whether the write went through. The third state is the honest problem. Treating an unknown outcome as "cancelled, nothing happened" is how a stopped run still changes a record, sends the message, or creates the draft, and the operator finds out from the other system.&lt;/p&gt;

&lt;p&gt;So a cancelled run shouldn't close with a single status word. It closes with an account: what stopped, what landed before the signal arrived, what cleanup removed, and what is still unknown and needs a person to settle it in the target system. If the unknown list isn't empty, the run isn't cleanly cancelled yet, whatever the UI says.&lt;/p&gt;

&lt;p&gt;Child runs widen the account. A supervisor that spawns background work needs cancellation to reach that work too, or the parent stops and the children keep going on the old instructions. LangChain's &lt;a href="https://docs.langchain.com/oss/python/deepagents/async-subagents" rel="noopener noreferrer"&gt;async subagents&lt;/a&gt; documentation makes this an explicit supervisor tool: cancelling a task calls the server's cancel on the run, marks the task cancelled, and, where earlier runs are still active, also cancels those. The propagation is a feature someone built, not a default of spawning work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup cannot live inside the thing you cancelled
&lt;/h2&gt;

&lt;p&gt;There's a trap in the obvious implementation. The run receives the signal, catches it, and tries to tidy up: delete the temporary file, release the hold, withdraw the draft. But the tidy-up is new work scheduled inside a scope that has just been cancelled, so it gets cancelled too. Temporal's scopes make the fix explicit: cleanup runs in a &lt;a href="https://docs.temporal.io/develop/typescript/workflows/cancellation-scopes" rel="noopener noreferrer"&gt;nonCancellable scope&lt;/a&gt;, protected from the very signal that triggered it.&lt;/p&gt;

&lt;p&gt;The agent version is the same shape. The component that runs cleanup, a reaper outside the run or a protected section of the runner, must not take instructions from the run it's cleaning up after, and it needs its own small budget of time and tool calls. Cleanup that can be starved by the cancellation itself is decoration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F70jvcw3kfvccxz18wjkk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F70jvcw3kfvccxz18wjkk.webp" alt="What the operator sees after the cancel. One item landed before the signal arrived, so it stays, flagged for a person. Nothing on the report is listed as unknown." width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What the operator sees after the cancel. One item landed before the signal arrived, so it stays, flagged for a person. Nothing on the report is listed as unknown.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two limits are worth stating plainly. First, cancellation is never instant, and UIs should stop pretending otherwise: there's always a window where the request is travelling and the in-flight call hasn't reported back, and the status during that window is "cancelling," not "cancelled." Second, some tools offer no way to check in and no way to withdraw a request once sent. For those, the honest options are waiting for the outcome before closing the run, or recording the unknown and handing it to a person. A confident green checkmark isn't on the list.&lt;/p&gt;

&lt;p&gt;None of this argues against giving people a stop button. It argues for building the plumbing the button implies: a signal that propagates to child runs and tool calls, check-ins from anything long-running, a protected cleanup path, and a closing report that separates what landed from what didn't. A run you can stop cleanly is also, not coincidentally, a run you understand while it's going.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work with us
&lt;/h2&gt;

&lt;p&gt;If stopping one of your agents today clears the screen while the work carries on underneath, we can help you build the version where the signal reaches the tool calls, the child runs, and the cleanup. Tell us about the deployment through &lt;a href="https://trida.ai/intake" rel="noopener noreferrer"&gt;Trida AI's intake&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tridaai</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>Dead Letter Queues for Agent Failures</title>
      <dc:creator>FDE at Trida AI</dc:creator>
      <pubDate>Fri, 06 Nov 2026 12:18:29 +0000</pubDate>
      <link>https://dev.to/tridaai/dead-letter-queues-for-agent-failures-4alm</link>
      <guid>https://dev.to/tridaai/dead-letter-queues-for-agent-failures-4alm</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://trida.ai/blog/dead-letter-queues-for-agent-failures" rel="noopener noreferrer"&gt;Trida AI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A failed agent run usually ends in the worst place it can: a log line. The run retried, spent its budget, gave up, and the only evidence is a stack trace in a stream that scrolls past thousands of other lines. Nobody owns it. A week later the same failure has happened forty more times, and each one was news.&lt;/p&gt;

&lt;p&gt;Messaging systems settled this problem years ago. When a message can't be processed, it doesn't vanish and it doesn't block the queue behind it. It goes to a dead letter queue, a holding place built for inspection. Amazon SQS describes &lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html" rel="noopener noreferrer"&gt;dead-letter queues&lt;/a&gt; as the place to isolate messages that failed processing so you can work out why. Google Cloud Pub/Sub forwards a message to a &lt;a href="https://cloud.google.com/pubsub/docs/handling-failures#dead_letter_topic" rel="noopener noreferrer"&gt;dead-letter topic&lt;/a&gt; after roughly a configured number of delivery attempts (five by default), wrapping the original with attributes that identify where it came from. Azure Service Bus adds a reason and a description to every dead-lettered message, and is blunt about the other half of the contract: there is &lt;a href="https://learn.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dead-letter-queues" rel="noopener noreferrer"&gt;no automatic cleanup&lt;/a&gt;, so messages sit there until someone retrieves them.&lt;/p&gt;

&lt;p&gt;Agent runs need the same destination, and most don't have one. The shape of the problem is familiar (retries exhausted, failure made permanent, work set aside for a human), but the unit is different. A message is a payload. A run is a piece of work that may have read three systems, written to one of them, and stopped halfway through a plan. Parking that safely takes more than a queue. It takes a record.&lt;/p&gt;

&lt;h2&gt;
  
  
  A log is not a queue
&lt;/h2&gt;

&lt;p&gt;Logs answer "what happened?" for someone who already knows where to look. A dead letter queue answers a different question: "what work is still owed, and who owes the next move?" That difference is operational, not cosmetic. A queue has an owner, a count, and an age for its oldest entry. It can alarm when it grows. Items leave it one of two ways, replayed or resolved by a person, and both are deliberate acts.&lt;/p&gt;

&lt;p&gt;So the first design decision is boring and important: terminal failure is a state, not an event. When a run exhausts its retries or its budget, the runner doesn't just report an error and exit. It writes a dead letter record and stops. Everything after that (inspection, the fix, the replay, the handoff) works from that record.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3yl64cwp97b0x6vwbain.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3yl64cwp97b0x6vwbain.webp" alt="Where a failed run goes. The runner parks a terminal failure as a record; a person either clears it for replay from its checkpoint or takes it over with the record attached." width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Where a failed run goes. The runner parks a terminal failure as a record; a person either clears it for replay from its checkpoint or takes it over with the record attached.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the record has to hold
&lt;/h2&gt;

&lt;p&gt;A dead letter record for a message can be thin: the payload, a reason code, a delivery count. A run's record has to carry enough for a stranger to make a safe decision about work they didn't watch happen.&lt;/p&gt;

&lt;p&gt;The original task, first: the instruction and inputs the run started from, snapshotted rather than referenced, because the source may have changed since. Then the failure, described in a small fixed vocabulary (tool timeout, invalid input, permission denied, output rejected by a checker), because free-text errors can't be counted, and uncounted failures can't be fixed in batches. The attempt history belongs beside it: how many tries, at what, and where the run stopped, with a pointer to its checkpoint and its trace.&lt;/p&gt;

&lt;p&gt;The field people skip is the side-effect register: what the run already changed in the world before it stopped. Which writes landed, under which idempotency keys, and, just as explicitly, which ones are unknown. That register is what separates a replayable failure from a dangerous one, and it's cheap to write at the moment of failure and nearly impossible to reconstruct a day later.&lt;/p&gt;

&lt;p&gt;Two smaller fields earn their space. An owner, because an unowned queue is a landfill with good tooling. And an expiry, because a failure nobody has looked at in thirty days is rarely worth replaying. The world it belonged to has moved on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4a50rir29p8h5z4qtqhq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4a50rir29p8h5z4qtqhq.webp" alt="One dead letter record. The replay decision is written down with the reason, and the unknown side effect — not the timeout — is what sends this one to a person." width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One dead letter record. The replay decision is written down with the reason, and the unknown side effect — not the timeout — is what sends this one to a person.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Replay is the dangerous part
&lt;/h2&gt;

&lt;p&gt;Getting a failed message back is a solved, almost casual operation: SQS calls it &lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-configure-dead-letter-queue-redrive.html" rel="noopener noreferrer"&gt;redrive&lt;/a&gt;, and the message returns to its source queue. Replay an agent run the same casual way and you can do real damage. The run may have already created the draft, sent the first email, or placed the hold. Starting it again from the top repeats whatever landed.&lt;/p&gt;

&lt;p&gt;So replay has a gate, and the gate reads the side-effect register. If every landed effect is recorded and the replay resumes from the checkpoint reusing the original idempotency keys, it can go. If any effect is marked unknown (a tool call that timed out after it may have committed), the record doesn't replay at all. It goes to a person, with the unknown named in plain language, and the person settles that one question before anything resumes. One ambiguous write should cost a human two minutes, not a duplicate payment.&lt;/p&gt;

&lt;p&gt;The queue also pays rent in aggregate. Read as a pile, dead letter records are a bug report against your own system: the same failure class forty times is a broken tool contract or a bad instruction, not forty accidents. The fix lands in the tool or the prompt, the waiting records replay against the fixed system, and the class empties. A queue that only ever gets replayed, never read, is a retry loop with extra storage.&lt;/p&gt;

&lt;p&gt;Where this stops helping: small deployments. If your agents fail terminally twice a week, a table and a named owner give you most of this; the full apparatus is for fleets where failures arrive faster than anyone can read logs. And the record itself needs care — task snapshots can hold personal or commercial data, so redact on the way in and keep retention short. A dead letter queue that becomes a second, unguarded copy of your inputs has traded one problem for a worse one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work with us
&lt;/h2&gt;

&lt;p&gt;If your agents' failures currently end as log lines, retried, dropped, and rediscovered weeks later, we can help you give them a destination: records a person can decide from, and replays that are safe to run. Tell us about the deployment through &lt;a href="https://trida.ai/intake" rel="noopener noreferrer"&gt;Trida AI's intake&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>tridaai</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>Agent Credentials Should Expire</title>
      <dc:creator>FDE at Trida AI</dc:creator>
      <pubDate>Thu, 05 Nov 2026 12:26:14 +0000</pubDate>
      <link>https://dev.to/tridaai/agent-credentials-should-expire-5be9</link>
      <guid>https://dev.to/tridaai/agent-credentials-should-expire-5be9</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://trida.ai/blog/agent-credentials-should-expire" rel="noopener noreferrer"&gt;Trida AI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hand an agent a key that never expires and you have not really given the agent access. You have given access to whoever eventually finds that key, for as long as they care to use it. Keys leak out of ordinary systems often enough. An agent raises the odds, because handling material and passing it along is most of its job.&lt;/p&gt;

&lt;p&gt;Our position is simple enough to fit on an index card: an agent's credential should be born with the task, scoped to that task, and dead shortly after it. If a deployment cannot say when its agents' credentials expire, the honest answer is probably that they don't, and that is the problem worth fixing first.&lt;/p&gt;

&lt;h2&gt;
  
  
  A key does not stay where you put it
&lt;/h2&gt;

&lt;p&gt;A conventional service holds a credential in one place, a config store or an environment variable, and uses it from there. An agent moves things. The key, or something that can be exchanged for it, can surface in a prompt, ride along in a tool call, get written into a run log, be quoted in a summary the agent writes about its own work, or travel inside the context handed to a second agent. None of that requires an attacker. It is the normal plumbing of these systems, doing what it was built to do.&lt;/p&gt;

&lt;p&gt;That changes what a leak means. With a human operator, a copied key is an incident. With an agent, copies are a by-product of ordinary operation, scattered across stores that were designed for reading, not for secrecy. You cannot reliably hunt down every copy after the fact. You can make every copy worthless on a schedule you choose in advance. That is what expiry is for.&lt;/p&gt;

&lt;p&gt;Scope matters as much as lifetime, and the two decisions belong together. A grant that can read one collection for fifteen minutes is a different object from a grant that can write anywhere for a year, even if both are called "the API key" in a config file. Expiry forces the scope conversation, because a credential that has to be re-issued for each task gets its permissions re-asked for each task too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The machinery already exists
&lt;/h2&gt;

&lt;p&gt;None of this needs inventing. Cloud platforms settled the pattern years ago for ordinary workloads. The &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/navigating-gdpr-compliance/temporary-access-tokens-through-aws-sts.html" rel="noopener noreferrer"&gt;AWS Security Token Service&lt;/a&gt; issues temporary credentials that work much like long-term keys, except that they are generated on request rather than stored, are valid for a configured span from fifteen minutes up to twelve hours, and stop being recognised at all once they expire. AWS makes the quiet point that follows from that: a credential with a limited lifespan does not have to be rotated, and it does not have to be explicitly revoked when the work ends. Time does it.&lt;/p&gt;

&lt;p&gt;Google Cloud lands in the same place from the service account side. Its documentation calls obtaining &lt;a href="https://cloud.google.com/iam/docs/service-account-creds" rel="noopener noreferrer"&gt;short-lived credentials&lt;/a&gt; the most secure way to authenticate as a service account, with access tokens expiring after one hour by default. It also blocks a neat circumvention: a short-lived credential may not be used to mint a fresh token for the same service account, because that would let a stolen token refresh itself indefinitely. Expiry that can be quietly extended by the holder is not expiry.&lt;/p&gt;

&lt;p&gt;Agents are simply late to a settled practice. The workload changed, from a service with a stable job to a system that takes a new instruction every few minutes, and the credential should change with it: one grant per task, sized to the task, rather than one standing key behind every task the system will ever run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6eeetm6lkdjnrj3kol1w.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6eeetm6lkdjnrj3kol1w.webp" alt="Two ways to give an agent access to the same tool: one standing key that spreads into prompts and logs, and a broker that issues a short grant per task and injects it when the call is made." width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Two ways to give an agent access to the same tool: one standing key that spreads into prompts and logs, and a broker that issues a short grant per task and injects it when the call is made.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the credential out of the model's hands
&lt;/h2&gt;

&lt;p&gt;Expiry alone still leaves the raw credential passing through a system that writes things down. The stronger shape puts a broker between the agent and the credential. The agent asks the broker for access to a named tool, for a named task. The broker checks that request against policy, obtains or mints a short grant, and the tool gateway attaches that grant to the outgoing call. The model sees that the call succeeded or failed. It never sees the thing that made the call authorised.&lt;/p&gt;

&lt;p&gt;This is the arrangement the &lt;a href="http://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html" rel="noopener noreferrer"&gt;OWASP secrets guidance&lt;/a&gt; points at with dynamic secrets: credentials generated on demand rather than retrieved from a drawer, given a short lease, and revoked when they are no longer needed, which cuts down how much any one credential can be reused. Applied to agents, the payoff is concrete. There is nothing in the prompt to leak, nothing in the transcript to scrape, and nothing in a handoff packet that quietly carries write access to the next run. A stolen transcript becomes a record of work, not a set of working keys.&lt;/p&gt;

&lt;p&gt;Renewal belongs to the broker as well, and it should be a fresh decision, not an automatic extension. When a grant runs out mid-task, the broker re-checks the task before issuing another: is this still the job that was approved, with the same scope? Most renewals will pass in milliseconds. The point is that they are checked at all, so a run that has wandered somewhere new does not drag its original permissions along with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhlv9o3hh16u3kr4hemsz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhlv9o3hh16u3kr4hemsz.webp" alt="One grant followed from issue to refusal: read-only export access issued for fifteen minutes, accepted once, then refused when the same token was replayed after expiry. Example content, invented for illustration." width="800" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One grant followed from issue to refusal: read-only export access issued for fifteen minutes, accepted once, then refused when the same token was replayed after expiry. Example content, invented for illustration.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this gets awkward
&lt;/h2&gt;

&lt;p&gt;Plenty of the tools agents need to reach do not offer short-lived credentials at all. Plenty of SaaS products still work the older way: a single API key per account, with no expiry, no per-task scope, and no way to ask for less. There the broker earns its keep differently: it holds the one standing key, never releases it to the agent, and enforces the expiry and scope on its own side of the gateway, by refusing calls that fall outside the current task's grant. The underlying key stays long-lived. The agent's effective access does not.&lt;/p&gt;

&lt;p&gt;Long tasks are the other friction. A research run or a migration that legitimately takes hours will cross several grant windows, and a grant that dies in the middle of a multi-step write can leave the work half-applied. The answer is renewal at step boundaries, arranged before the work starts, plus the discipline covered by idempotent tool design: a step that is retried after a renewal must be safe to run twice. What you should not do is respond to one awkward long task by issuing day-long grants to everything.&lt;/p&gt;

&lt;p&gt;And the broker itself becomes a high-value target, which is worth saying plainly. You have concentrated the ability to mint access in one component. It needs the boring protections any such component needs, tight policy, a full record of every grant it issues, and no path by which the agent can talk it into a wider scope than the task was approved for. Short-lived credentials shrink the blast radius of almost every failure except a failure of the broker. Staff it accordingly.&lt;/p&gt;

&lt;p&gt;None of these are reasons to keep handing agents standing keys. They are the shape of the work once you stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work with us
&lt;/h2&gt;

&lt;p&gt;If your agents are running on keys that never expire, bring that setup through &lt;a href="https://trida.ai/intake" rel="noopener noreferrer"&gt;Trida AI's intake&lt;/a&gt; and we will work out how to put a broker and a per-task grant between the model and the credential, starting with the tools that carry the most risk.&lt;/p&gt;

</description>
      <category>tridaai</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>Agent Answers Need Receipts</title>
      <dc:creator>FDE at Trida AI</dc:creator>
      <pubDate>Wed, 04 Nov 2026 12:10:34 +0000</pubDate>
      <link>https://dev.to/tridaai/agent-answers-need-receipts-f9b</link>
      <guid>https://dev.to/tridaai/agent-answers-need-receipts-f9b</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://trida.ai/blog/agent-answers-need-receipts" rel="noopener noreferrer"&gt;Trida AI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"Where did nineteen days come from?"&lt;/p&gt;

&lt;p&gt;Picture a routine review. An agent has summarised a supplier's documents and put a lead time at nineteen days. The number looks ordinary, sitting in a tidy paragraph next to two others nobody questions. Then someone asks which document said nineteen, and the room goes quiet. The trace shows the searches, the reads, a long generation. What it cannot show is which passage that one number rested on. Nobody kept the connection between the claim and its source.&lt;/p&gt;

&lt;p&gt;Most agent systems keep a good record of the run and a poor record of the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  A trace answers a different question
&lt;/h2&gt;

&lt;p&gt;A trace is an operator's record. It tells you the order of operations, what each call received and returned, where the time went. The &lt;a href="https://openai.github.io/openai-agents-python/tracing/" rel="noopener noreferrer"&gt;OpenAI Agents SDK&lt;/a&gt; traces runs this way by default: generation spans hold the model's inputs and outputs, function spans hold a tool call's arguments and its result, and you can turn that capture off because the contents may be sensitive. Useful, and necessary. Also not the reader's question.&lt;/p&gt;

&lt;p&gt;The reader's question is narrower and ruder: this sentence, the one I am about to act on: what is it standing on? A trace can help an engineer reconstruct an answer after the fact, the way a flight recorder helps investigators. A receipt is the thing you hand over at the time, before anyone has to reconstruct anything.&lt;/p&gt;

&lt;p&gt;The idea is old. The &lt;a href="https://www.w3.org/TR/2012/WD-prov-primer-20120503/" rel="noopener noreferrer"&gt;W3C PROV family&lt;/a&gt; modelled provenance as entities, activities, and agents: a thing, the process that produced it, and whoever bears responsibility for that process. Agent answers fit that shape well. The claim is the entity. The retrieval is the activity. The system that served the answer is the agent who stands behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attach the receipt to the claim
&lt;/h2&gt;

&lt;p&gt;Answer-level citations are where good intentions go soft. A paragraph ends with two links, and the reader guesses which link supports which sentence, or whether either supports the number in the middle. Receipts earn their name at claim level: one claim, one record, four fields.&lt;/p&gt;

&lt;p&gt;The source, named exactly — the document, the record, the tool result, not "the database". The passage inside it, located so a person can land on it: a page range, a character range, a row key. The time it was read, because sources change and a receipt from March should not underwrite a claim made in November. And the claim itself, stored as written, so a later edit cannot borrow a receipt issued for different words.&lt;/p&gt;

&lt;p&gt;Some of this machinery now ships with the models. &lt;a href="https://platform.claude.com/docs/en/build-with-claude/citations" rel="noopener noreferrer"&gt;Claude citations&lt;/a&gt; return structured objects rather than prose promises: the cited text, which document it came from, and a location that is a character range for plain text, a page range for a PDF, or a block range for custom content. That is the right shape. It is not, on its own, a guarantee. Anthropic's own guidance for extracting fields this way ends with an instruction to check that each field has a value and at least one citation, because the API guarantees neither. The check is yours to run. A claim that comes back without a receipt has to go somewhere you decided in advance: held for a person, rewritten to say less, or dropped. What it cannot do is ship wearing the same confidence as its supported neighbours.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqb2s87hfiss0xwk100qm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqb2s87hfiss0xwk100qm.webp" alt="One claim's path through the system: the tool result is stored as a receipt before the answer is written, and a checker holds any claim that arrives without one." width="800" height="580"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One claim's path through the system: the tool result is stored as a receipt before the answer is written, and a checker holds any claim that arrives without one.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Receipts have to survive the machinery around the answer, too. Summaries compress, handoffs pass context along, and both are fine places for a source to fall off a claim. Pass receipt identifiers along with the text they support, the way you would pass a primary key, and let the checker follow them back to the stored passage. If the passage cannot be opened, the claim is unsupported again.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the reader actually sees
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqtioaxzmjdigu7n3qtg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyqtioaxzmjdigu7n3qtg.webp" alt="One short answer, three claims: two carry receipts a reader can open, and the third, which had no receipt for its date, was rewritten to say only what its source supports. Example content, invented for illustration." width="800" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One short answer, three claims: two carry receipts a reader can open, and the third, which had no receipt for its date, was rewritten to say only what its source supports. Example content, invented for illustration.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Notice what changed for the third claim. It was not deleted, and it was not left standing on a plausible tone of voice. It shrank to fit its evidence. That is most of the daily value here: receipts rarely expose a dramatic fabrication. They expose the sentence that drifted a little further than its source, the figure that was true of last quarter, the commitment a document mentioned as a possibility and the answer repeated as a plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where receipts run out
&lt;/h2&gt;

&lt;p&gt;They only cover claims that come from somewhere. A recommendation, a judgment, a forecast — these rest on reasoning, and dressing them in citations borrowed from nearby facts would be worse than leaving them bare. Mark them as the system's judgment and let the reader price them that way.&lt;/p&gt;

&lt;p&gt;Coverage costs, too. Claim-level checking adds a pass to every run, the receipt store becomes one more system holding possibly sensitive passages, and a team that demands receipts for trivia will train its agents to write answers with no checkable claims in them at all. Receipts belong where a reader will act: figures, dates, commitments, statuses. The rest can travel lighter.&lt;/p&gt;

&lt;p&gt;And a receipt proves where a claim came from, not that the source was right. A wrong document produces a perfectly valid receipt. That still helps: the argument moves from "the agent said so" to the document itself, in one click, and the document can be fixed at its root.&lt;/p&gt;

&lt;p&gt;Keep the passage, not a pointer to it. Attach it to the claim, not the answer. Hold the claim that arrives without one. Three dull rules, and the question at the top of this post — where did this come from — gets an answer in seconds instead of a reconstruction project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work with us
&lt;/h2&gt;

&lt;p&gt;If your agents produce answers your team cannot check without replaying the whole run, the missing piece is a receipt layer, not a longer trace. Bring the answers your readers act on through &lt;a href="https://trida.ai/intake" rel="noopener noreferrer"&gt;Trida AI's intake&lt;/a&gt; and we will work out which claims need to carry their sources with them.&lt;/p&gt;

</description>
      <category>tridaai</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>Why Google Splits the Designer from the Reviewer</title>
      <dc:creator>FDE at Trida AI</dc:creator>
      <pubDate>Wed, 04 Nov 2026 00:35:36 +0000</pubDate>
      <link>https://dev.to/tridaai/why-google-splits-the-designer-from-the-reviewer-1mh8</link>
      <guid>https://dev.to/tridaai/why-google-splits-the-designer-from-the-reviewer-1mh8</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://trida.ai/blog/why-google-splits-the-designer-from-the-reviewer" rel="noopener noreferrer"&gt;Trida AI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ask the session that designed a feature to review the design document, and it will call the document clear. It remembers the argument behind every sentence: the trade-off that took twenty minutes to settle, the edge case somebody raised and answered, almost none of it written down. The session reads a gap and fills it from memory, so the review certifies a document a fresh reader would trip over immediately.&lt;/p&gt;

&lt;p&gt;You know this failure from your own writing. Self-proofreading is unreliable, because you read what you meant to say. A long design session has the same blind spot, backed by perfect recall of everything except what reached the page.&lt;/p&gt;

&lt;p&gt;Google's response is to build the split into the workflow. The Elephant-Goldfish Model (EGM) plugin, &lt;a href="https://opensource.googleblog.com/2026/10/introducing-elephant-goldfish-model-plugin.html" rel="noopener noreferrer"&gt;open-sourced in October 2026&lt;/a&gt;, separates two kinds of AI session and keeps them apart. One designs. Others, who never saw the designing, review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two kinds of session, one document
&lt;/h2&gt;

&lt;p&gt;The Elephant is the context-rich session. It holds the architecture, the requirements and the decisions, and writes no code. Its job is conversation: interviewing you, debating options, challenging assumptions. The Goldfish are the opposite. Each is a fresh, stateless session that gets the design document and its referenced sources, and nothing else. No transcript, no memory of the debate.&lt;/p&gt;

&lt;p&gt;That makes the document the only channel from designer to reviewers. Anything the Elephant knows that never got written down does not survive the trip.&lt;/p&gt;

&lt;p&gt;The workflow runs in three phases. In egm-brainstorm, you and the Elephant shape the idea under a no-code rule. In egm-draft, the two of you produce a Markdown design document: the problem, the technical plan, the alternatives considered, and an implementation plan naming the specific files to change. Then egm-review hands it to the Goldfish, in three checks.&lt;/p&gt;

&lt;p&gt;First, a comprehension check: a Goldfish reads the document and has to explain the architecture and intent back. If it can't restate the plan accurately, the document is ambiguous and goes back. Second, an antagonistic review: several Goldfish act as skeptical reviewers, hunting edge cases, hidden dependencies, and logical gaps, and their findings go back to the Elephant. Third, an implementation readiness check: a final Goldfish makes a go/no-go call on whether file paths, data structures, and function signatures are present and unambiguous.&lt;/p&gt;

&lt;p&gt;The plugin is Apache 2.0 and lives in &lt;a href="https://github.com/google/elephant-goldfish-model" rel="noopener noreferrer"&gt;the EGM repository&lt;/a&gt;, built to bring EGM into Antigravity first, with installs available for Claude Code and Codex. One README rule shows how seriously the split is taken: Goldfish reviews require fresh subagent contexts, and if the host can't create them, EGM stops the review. Independence that cannot be established is not faked. Google says thousands of Googlers use this plugin every day to ship production Google code.&lt;/p&gt;

&lt;p&gt;The model comes from Dave Rensin's essay "Elephants, Goldfish, and the New Golden Age of Software Engineering." Rensin is a Distinguished Engineer at Google. Its thesis fits in one line: "Design is the new code."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwpvtnq5iav0znrrm8tv3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwpvtnq5iav0znrrm8tv3.webp" alt="The EGM split: the Elephant designs, the document carries the design, zero-context Goldfish review it, and a person approves before any code is written." width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The EGM split: the Elephant designs, the document carries the design, zero-context Goldfish review it, and a person approves before any code is written.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Independence you can't prompt for
&lt;/h2&gt;

&lt;p&gt;The obvious shortcut is one session, instructed to review its own design objectively: list your assumptions, argue the other side. The instruction cannot remove the problem, and the reason is mechanical. The memory that creates the blind spot is still in the room. A prompt can change what a session says; it cannot change what the session knows. EGM changes what the reviewer knows, by handing the review to sessions that never had it. Independence is not an attitude. It is a property of the topology: separate sessions, separate context, enforced the way you would enforce it between people.&lt;/p&gt;

&lt;p&gt;The comprehension check is the sharpest because of what it tests. A reviewer who has to teach the design back produces a restatement you can compare with your intent. Where the restatement drifts, the document is at fault: it read fine line by line and did not add up as a whole. That is the method's real strength, and its real boundary: the review tests whether a design document stands on its own. Whether the design is right is a different question, and no Goldfish can answer it.&lt;/p&gt;

&lt;p&gt;The reviewer set is split by lens rather than run as one general pass: the repository ships separate personas for comprehension, design criticism, structure, and style, plus a synthesizer that reconciles what they find. Reviews that only ever add are how design documents grow fat; separate lenses keep each pass honest about what it is checking.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it catches, and what walks past it
&lt;/h2&gt;

&lt;p&gt;What the Goldfish catch is document failure: ambiguity, unstated assumptions, missing implementation detail. The readiness bar makes the standard concrete: whoever implements the design should not have to make a single design choice. If a decision is still open, the document is not finished.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F71439cu5e29hqkkkva1y.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F71439cu5e29hqkkkva1y.webp" alt="An illustrative design excerpt in the EGM format: webhook delivery, with Goldfish findings pinned to the lines they question." width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An illustrative design excerpt in the EGM format: webhook delivery, with Goldfish findings pinned to the lines they question.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What walks past is a design that is wrong about the world. A Goldfish can check the document against itself and against the code it cites. It cannot check the decision against reality: the traffic figure in a dashboard nobody attached, the constraint a stakeholder mentioned in a hallway. That judgment stays with the person who approves the design. Fresh eyes find what is missing from the page; they say nothing about whether what is on the page should happen.&lt;/p&gt;

&lt;p&gt;Two more limits. The default reviewer set has no security pass: &lt;a href="https://www.helpnetsecurity.com/2026/10/26/google-elephant-goldfish-model-plugin/" rel="noopener noreferrer"&gt;security flaws&lt;/a&gt; are not on the reviewers' list by name, so a team that wants a threat review has to set one up. The personas are customizable, so a security-minded Goldfish is possible, but nobody gets one by default. And the reviewers usually run on the same model as the designer, with a clean context. That buys independence of memory, not independence of judgment; a blind spot built into the model itself walks straight through the context wall.&lt;/p&gt;

&lt;p&gt;The cost is real: several fresh reviewer runs per design, plus your time triaging what the reviewers return. Google's own guidance is to skip EGM for a small, clear bug fix. It is aimed at work where design decisions exist to be gotten wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern is bigger than code review
&lt;/h2&gt;

&lt;p&gt;Step back, and the shape is familiar to anyone running agents in production. Whoever produces an output should not be its only evaluator. Teams say this about evals, then wire a single session to grade its own homework because it is convenient. EGM is what the principle looks like when it is enforced by architecture instead of requested in a system prompt: separate context, separate session, and a hard stop when separation can't be provided.&lt;/p&gt;

&lt;p&gt;It is the same lesson as isolating untrusted content from instructions, from the other direction. What a session has seen determines what it can check. A session steeped in the design conversation is compromised as a reviewer the way a session that ingested a hostile document is compromised as an actor: not through bad intent, but through context it can't unsee. Google putting context isolation into a plugin its engineers use on production code moves it from principle into tooling, and we think it's the right direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stops
&lt;/h2&gt;

&lt;p&gt;Be clear about what EGM is not. It is a workflow plugin — Markdown rules, reviewer personas, structured checks — and the release adds no new model; what it encodes is discipline as tooling.&lt;/p&gt;

&lt;p&gt;The adoption claim is Google's. Thousands of Googlers use the plugin every day, on Google's account, with no published figures for defects caught or rework avoided, and no published empirical evaluation behind the central claim. The mechanism is plausible; the claim remains untested, and the evidence is still anecdote. Launch coverage mostly took the claim at face value: &lt;a href="https://betanews.com/article/google-open-sources-its-elephant-goldfish-model-plugin-for-ai-coding/" rel="noopener noreferrer"&gt;BetaNews' report&lt;/a&gt; on the release reasoned that the model's internal use at Google "should give users faith in its capabilities." Internal use does prove the workflow survived contact with a large engineering organization. It says nothing about defects caught or rework avoided, which is the number that would settle the argument. There is a failure mode in the other direction: run every change through a three-phase design ritual and the document becomes the product, polished past the point where an afternoon and a test would settle it. EGM does not replace tests or human judgment. The repository says so itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work with us
&lt;/h2&gt;

&lt;p&gt;If your team is letting agents write code, one quality lever you control directly is the review topology: who checks the work, and what they are allowed to remember. If you want help building that discipline around your own agent workflows, start with &lt;a href="https://trida.ai/intake" rel="noopener noreferrer"&gt;Trida AI's intake&lt;/a&gt; and tell Trida AI what you are building.&lt;/p&gt;

</description>
      <category>tridaai</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>Agents Need a Degraded Mode</title>
      <dc:creator>FDE at Trida AI</dc:creator>
      <pubDate>Tue, 03 Nov 2026 12:12:41 +0000</pubDate>
      <link>https://dev.to/tridaai/agents-need-a-degraded-mode-252c</link>
      <guid>https://dev.to/tridaai/agents-need-a-degraded-mode-252c</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://trida.ai/blog/agents-need-a-degraded-mode" rel="noopener noreferrer"&gt;Trida AI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most production agents have exactly two states: working, and broken. While every dependency is healthy they answer, act, and write. The moment one fails (a model endpoint timing out, a search tool returning errors, a data source gone stale) the run either dies on an error screen or does something worse. It carries on at full confidence and fills the gap from memory.&lt;/p&gt;

&lt;p&gt;Neither state earns trust. The error throws away the work the run had already done, and the user starts over. The confident continuation keeps the shape of a good answer after the evidence under it has gone. There is a third state, and it has to be designed while the system is healthy, because it cannot be improvised during an incident: a degraded mode, where the agent does less, says so plainly, and keeps doing the smaller job well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shrink the scope, keep the honesty
&lt;/h2&gt;

&lt;p&gt;The idea predates agents by decades. Google's SRE book describes &lt;a href="https://sre.google/sre-book/handling-overload/" rel="noopener noreferrer"&gt;degraded responses&lt;/a&gt; as answers that are less accurate, or carry less data than normal, but are cheaper to produce: search only a small percentage of the candidate set rather than the entire corpus, or answer from a local copy that may not be fully up to date. The service stays up. It promises less.&lt;/p&gt;

&lt;p&gt;For an agent, promising less rarely means a shorter answer. It means a narrower job. When the write path is down, the agent goes read-only: it can look, summarise, and prepare, and it stops offering to change anything. When fresh data is unreachable, it answers from the last good copy and puts that copy's date in the answer, where the reader can see it. When the model call itself keeps failing, the run queues the request with its context intact instead of spending the whole budget on retries. Each mode is a smaller promise the system can still keep.&lt;/p&gt;

&lt;p&gt;The label carries the weight here. A reduced answer formatted to look like a full one misleads the reader even when every sentence in it is accurate, because the reader prices it as a full answer. So the mode belongs in two places: in the response, in ordinary words, and in the trace, as a recorded fact. Which dependency failed, when the mode started, what the agent has stopped doing. If the trace cannot answer those three, the mode exists in the prompt and nowhere else.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdl9yngulac0eo3dz5tta.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdl9yngulac0eo3dz5tta.webp" alt="How the mode gets set: dependency health signals feed one controller, which sets the run's mode and writes the mode and its reason to the trace." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How the mode gets set: dependency health signals feed one controller, which sets the run's mode and writes the mode and its reason to the trace.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The fallback can be the outage
&lt;/h2&gt;

&lt;p&gt;Restraint matters as much as the modes themselves. Amazon's engineers make that case in &lt;a href="https://aws.amazon.com/builders-library/avoiding-fallback-in-distributed-systems/" rel="noopener noreferrer"&gt;Avoiding fallback in distributed systems&lt;/a&gt;: fallback paths are hard to test, they can fail on their own, and they can place unpredictable load on the parts of the system already struggling. Their example is blunt. When the caches all failed around the same time, web servers fell back to querying the database directly; the database locked up under that load, and a partial problem (shipping speeds missing from product pages) became a full outage that also halted fulfilment centres.&lt;/p&gt;

&lt;p&gt;Agent fallbacks have the same shape. Fail over to a second model and every run in the fleet arrives at that model at once, carrying longer contexts than it usually sees. Fall back from a failing search tool to the model's memory and the failure goes quiet: answers keep flowing, now ungrounded, and nobody gets paged. A fallback that changes what an answer is made of is a mode change. Label it like one.&lt;/p&gt;

&lt;p&gt;The same article points at the escape hatch worth copying: convert fallback into failover by running both paths continuously and treating the second as an equally valid source, so it stays exercised and sized for the day it has to carry everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the quiet mode before you need it
&lt;/h2&gt;

&lt;p&gt;Google's chapter on &lt;a href="https://sre.google/sre-book/addressing-cascading-failures/" rel="noopener noreferrer"&gt;cascading failures&lt;/a&gt; holds the warning that applies to every rarely used path: "the code path you never use is the code path that (often) doesn't work." A mode entered twice a year gets entered with stale assumptions, a label nobody proofread, and a queue nobody monitors.&lt;/p&gt;

&lt;p&gt;So enter it on purpose. Send a small slice of ordinary traffic through read-only mode every week and read what comes back. Put the switch in the eval set: hand the agent a tool that returns errors and check whether it narrows its offer, labels the answer, and stops attempting writes. Then check recovery, the half teams forget. When the dependency comes back, the run should return to full mode on its own, held work should be re-checked before it is written, and anything that went stale while it waited should go to a person instead of going through.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvinhxgua953sk1yo9aez.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvinhxgua953sk1yo9aez.webp" alt="One run, while search was down: the mode change, the labelled answer, and the held work, including the update that went stale and was routed to a person. Example run, invented for illustration." width="800" height="605"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One run, while search was down: the mode change, the labelled answer, and the held work, including the update that went stale and was routed to a person. Example run, invented for illustration.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where degrading is the wrong answer
&lt;/h2&gt;

&lt;p&gt;Some actions have no honest reduced form. A payment that cannot be confirmed should not soften into a probable payment. A deletion, a send, a filing: when the safe subset is empty, the right degraded mode is a stop, with the context preserved and a person told why. Stopping is a mode too, as long as it is chosen and explained rather than stumbled into.&lt;/p&gt;

&lt;p&gt;Modes also cost. Each one is another behaviour to test, another trace shape to read at 2 a.m., another answer format for users to learn. A system with one dependency and forgiving users may need a single extra mode, or none. What no system needs is the unplanned middle, where the agent has quietly degraded and only the model knows.&lt;/p&gt;

&lt;p&gt;Decide the modes while the system is healthy. Write down, for each dependency, what the agent may still do when it fails, what it must stop doing, and what it tells the reader. That table is short. Skip it, and the first incident writes it for you, in production, at the worst available moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work with us
&lt;/h2&gt;

&lt;p&gt;If your agents fail whole when one dependency blinks, or keep answering long after they should have stopped trusting their inputs, the fix is a designed degraded mode, not a larger retry budget. Bring the dependency list through &lt;a href="https://trida.ai/intake" rel="noopener noreferrer"&gt;Trida AI's intake&lt;/a&gt; and we will map what each failure should cost you.&lt;/p&gt;

</description>
      <category>tridaai</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>Design Tools for the Model</title>
      <dc:creator>FDE at Trida AI</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:20:06 +0000</pubDate>
      <link>https://dev.to/tridaai/design-tools-for-the-model-115i</link>
      <guid>https://dev.to/tridaai/design-tools-for-the-model-115i</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://trida.ai/blog/design-tools-for-the-model" rel="noopener noreferrer"&gt;Trida AI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Picture two tools in the same agent. One is called &lt;code&gt;billing_search&lt;/code&gt;, described as "search records." The other is &lt;code&gt;support_search&lt;/code&gt;, also described as "search records." A user asks why a refund has not arrived, the model picks the billing tool, queries the wrong system with a well-formed call, and gets a clean, confident, useless answer back. Nothing crashed. The implementation of both tools may be excellent. The failure happened earlier, in the four lines of text the model was given to choose between them.&lt;/p&gt;

&lt;p&gt;That is the part of tool design teams underinvest in. A model never reads your code. It reads a name, a description, a schema for the arguments, and later, whatever the call returns. Everything it decides (which tool, with what inputs, and what to make of the result) is decided from those surfaces. Anthropic puts the underlying shift well in its guide to &lt;a href="https://www.anthropic.com/research/writing-tools-for-agents" rel="noopener noreferrer"&gt;writing effective tools&lt;/a&gt;: a tool is a contract between a deterministic system and a non-deterministic agent, and the agent is free to call it, ignore it, or ask a question instead. You are not writing a function signature for a compiler. You are briefing a capable colleague who will improvise.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://modelcontextprotocol.io/specification/2025-11-25/server/tools" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; makes the surface explicit. In that specification, at its core, a tool is a name, a description, an input schema, and optionally an output schema, and tools are model-controlled: the model discovers them and invokes them on its own reading of the request. There is no other channel. If the boundary between two tools matters, it has to be visible in what the model can read.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3bcjdmmiu8jzsmv21lge.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3bcjdmmiu8jzsmv21lge.webp" alt="The tool-calling loop: the model reads the definition, proposes a call, code validates and runs it, and a shaped result goes back for the next decision." width="800" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The tool-calling loop: the model reads the definition, proposes a call, code validates and runs it, and a shaped result goes back for the next decision.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The name does the first sort
&lt;/h2&gt;

&lt;p&gt;Before any description is read closely, the name is doing selection work. &lt;code&gt;search&lt;/code&gt; tells the model almost nothing. &lt;code&gt;support_tickets_search&lt;/code&gt; tells it which system, which records, and which job. Anthropic’s guidance is to namespace by service and resource, with examples along the lines of &lt;code&gt;asana_projects_search&lt;/code&gt; versus &lt;code&gt;asana_users_search&lt;/code&gt;, so that two tools with the same verb stop looking interchangeable. One honest caveat from the same source: whether a prefix or a suffix scheme tests better varies by model, so treat your naming scheme as something to measure, not a style rule to copy from a blog post. Ours included.&lt;/p&gt;

&lt;p&gt;Names also fail in the other direction. A name that promises more than the tool does (&lt;code&gt;get_full_customer_profile&lt;/code&gt; that returns only an email address) teaches the model to trust the label and stop checking the result. The name is a claim. Keep it to what the tool actually returns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Write the description for a reader who cannot ask you anything
&lt;/h2&gt;

&lt;p&gt;Both major guides land on the same test, from opposite directions. Anthropic says to describe the tool as you would to a new hire, making the implicit knowledge explicit: query syntax, vocabulary, how the underlying resources relate. OpenAI’s version is the &lt;a href="https://learn.chatgpt.com/api/docs/guides/function-calling" rel="noopener noreferrer"&gt;intern test&lt;/a&gt;: could a person use the function correctly given only what you gave the model? If not, the questions they would ask you are exactly what belongs in the description.&lt;/p&gt;

&lt;p&gt;In practice that means the description carries four things most API documentation leaves out. When to use the tool. When not to, including which neighbouring tool to prefer. What each parameter means in the domain’s terms, with &lt;code&gt;user_id&lt;/code&gt; instead of a bare &lt;code&gt;user&lt;/code&gt;. And what the result will look like, so the model can plan its next step before it calls.&lt;/p&gt;

&lt;p&gt;This is not polish. Anthropic reports that precise refinements to tool descriptions took Claude Sonnet 3.5 to state-of-the-art performance on SWE-bench Verified, and that a separate production problem, the model appending the current year to search queries and biasing the results, was corrected by improving a description, not by changing code. When a model misuses a tool in your own traces, read the description before you read the implementation. A good share of the time, the model did what the text told it to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let the schema refuse bad calls
&lt;/h2&gt;

&lt;p&gt;A description persuades. A schema constrains, and the constraint is the more reliable of the two. Where a value comes from a known set, an enum says so; two booleans that can contradict each other invite a call that is valid JSON and nonsense at the same time. Required fields should be the ones the tool genuinely cannot run without, and optional fields should be ones the model can sensibly omit.&lt;/p&gt;

&lt;p&gt;Two moves save more failures than any amount of description tuning. First, do not ask the model for a value your code already knows. If the run is already working on order 8819, that order ID belongs in your code path, not in a parameter the model can mistype. Second, fold steps that always happen together into one tool. A lookup that is always followed by a second lookup is really one tool split in two, and every seam between the halves is a place for the model to stop early, reorder, or pass the wrong identifier across. Anthropic makes the same point with its own examples: a &lt;code&gt;search_contacts&lt;/code&gt; over a &lt;code&gt;list_contacts&lt;/code&gt; the agent then has to page through, a single scheduling tool over separate list-and-create calls.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs5y1pltcd6wh7jqt9brj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs5y1pltcd6wh7jqt9brj.webp" alt="One tool definition, before and after: the same search, rewritten so the model can tell what it is for, when to use it, and what a good call looks like. Example values, invented for illustration." width="800" height="655"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One tool definition, before and after: the same search, rewritten so the model can tell what it is for, when to use it, and what a good call looks like. Example values, invented for illustration.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The result is an interface too
&lt;/h2&gt;

&lt;p&gt;What comes back gets the same design attention, because the result is what the next decision is made from. Return the fields the next step needs, in the form a reader can act on. Anthropic found that resolving cryptic identifiers into meaningful names measurably improved precision on retrieval tasks, and it recommends letting the agent choose the shape of the answer: a &lt;code&gt;response_format&lt;/code&gt; option returning a detailed response at 206 tokens or a concise one at 72. The point is not the specific numbers. It is that verbosity is a parameter the caller controls, not a fixed cost every call pays.&lt;/p&gt;

&lt;p&gt;Truncation deserves the same care. A result cut off mid-list with no note teaches the model the list ended. A result that says it was truncated, and that a narrower query would return the rest, gives the model a next move. The result is the only feedback the model gets about your system. Write it like it will be believed, because it will be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where better text stops helping
&lt;/h2&gt;

&lt;p&gt;None of this fixes a tool that should not exist. If two tools genuinely do the same job, no description will keep them sorted for long; merge them. If a tool hands the model authority it should not have, a clearer description makes misuse more reliable, not less. And descriptions are not free: they sit in the context of every run, so a description that grows by accretion, every past incident pasted in as a warning, eventually costs more attention than it saves. OpenAI’s guide adds a related caution: examples help many models use a tool correctly, and can hurt performance with reasoning models. There is no universal template. There is a prototype, an evaluation with real multi-step tasks, and the willingness to edit the text when the traces say the text is wrong.&lt;/p&gt;

&lt;p&gt;So review a tool the way its reader meets it. Cover the implementation. Read the name, the description, the schema, and a sample result, and ask whether a smart stranger could pick the right moment, fill in the right arguments, and understand what came back. If they could not, the model cannot either. It will just fail faster, and at greater scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work with us
&lt;/h2&gt;

&lt;p&gt;If your agents pick the wrong tool, fill in plausible-but-wrong arguments, or drown in their own results, the fix usually starts at the tool surface, not the model. Bring the tool set through &lt;a href="https://trida.ai/intake" rel="noopener noreferrer"&gt;Trida AI’s intake&lt;/a&gt; and we will review it the way the model reads it.&lt;/p&gt;

</description>
      <category>tridaai</category>
      <category>fieldnotes</category>
    </item>
    <item>
      <title>SMB Commerce Operations Agent Blueprint</title>
      <dc:creator>FDE at Trida AI</dc:creator>
      <pubDate>Mon, 02 Nov 2026 07:19:44 +0000</pubDate>
      <link>https://dev.to/tridaai/smb-commerce-operations-agent-blueprint-2i7</link>
      <guid>https://dev.to/tridaai/smb-commerce-operations-agent-blueprint-2i7</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://trida.ai/blog/smb-commerce-operations-agent-blueprint" rel="noopener noreferrer"&gt;Trida AI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A small online retailer's week lives in five tools that do not talk to each other: the storefront knows the orders, the payments tool knows the refunds, the ad account knows the spend, the email platform knows the flows, the books know the expenses. The owner's Sunday-night ritual — export, eyeball, worry — is where those five stories get reconciled, by a tired human, into decisions about stock, spend and copy. Meta saw the same ritual and productised it: Muse for Small Business puts a &lt;a href="https://ai.meta.com/blog/introducing-muse-spark-msl/" rel="noopener noreferrer"&gt;Muse Spark&lt;/a&gt; agent over exactly that connected stack. This blueprint is the open, governed version of that pattern — the same operating picture, the same drafts, but built so that every number can be traced, every draft is checked before it exists, and nothing leaves the building on the model's say-so.&lt;/p&gt;

&lt;p&gt;This open blueprint, &lt;a href="https://github.com/tridaai/blueprint-meta-muse-smb-commerce-operations" rel="noopener noreferrer"&gt;the SMB Commerce Operations Agent&lt;/a&gt;, watches a small home-goods retailer's orders, refunds, inventory, ad spend, email flows and expenses. The operating picture is computed in code; the findings come from versioned rule packs; the growth plan and the copy are drafted by the model through the &lt;a href="https://dev.meta.ai" rel="noopener noreferrer"&gt;Meta Model API&lt;/a&gt; — behind two gates Meta's own demonstrations popularised: a separate guardian screen, and a named human approval before anything sends, publishes or spends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Facts enter under contract, or not at all
&lt;/h2&gt;

&lt;p&gt;Everything starts at the ingest port, and the port is rude. Each feed — storefront, payments, warehouse, ads, email, books — arrives as a batch under a stated contract version, and every record must carry its source system and an as-of date. A record without provenance is refused; a batch under the wrong contract version is quarantined whole, with the violations listed — in the sample data, a test books batch is turned away entirely rather than partially absorbed. Replaying a batch lands nothing twice: the storefront's 2,184 orders replay as 2,184 duplicates and zero new facts. By the time anything computes, the record is 3,954 facts that all answer the question "where did you come from, and as of when?"&lt;/p&gt;

&lt;h2&gt;
  
  
  The picture, computed
&lt;/h2&gt;

&lt;p&gt;Over those facts, a ledger computes the year in Decimal arithmetic: gross revenue of $2,077,752, refunds of $38,144 (a rate of 1.84%), net revenue of $2,039,608 and gross margin of $1,155,009. The ledger is an insert-only, content-addressed artefact, and a verifier re-performs every figure by a second route from the stored facts. Tamper with a fact behind the store's back and verification does not produce a slightly different total — it names the tampered fact. Inventory cover is computed per product the same way: one linen throw is the tightest, 210 on hand at ten a day, 21.00 days of cover, out on January 25.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8105zu5e7ce23u58hira.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8105zu5e7ce23u58hira.webp" alt="The year, computed once and re-performed by a second route before anyone reads it." width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The year, computed once and re-performed by a second route before anyone reads it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Findings with thresholds, not vibes
&lt;/h2&gt;

&lt;p&gt;Diagnosis packs turn the ledger into findings, and each pack states its bars before it judges. The throw is flagged because 21.00 days sits under the pack's 24-day stockout horizon — a product with adequate cover is named nowhere, and one with no sales history reads no-data rather than zero-risk. The social ad set is flagged decaying: $26,000 of spend whose return fell from 4.0000 in its first week to 0.1704 in its last, judged only because it clears the pack's spend and history floors; the two steady sets are named nowhere, and a set below the floors would be recorded as unjudged, with the reason — never silently passed. The books yield a duplicate: the same vendor charging the same $349.00 on consecutive days, both fact identifiers on the finding's face. The refund pack flags one product returning at exactly 5.00% against a 4.00% bar, over enough volume to judge. And the email audit finds the flow that was never defined — winback — and drafts a proposal for it that is stamped, in so many words, as changing nothing.&lt;/p&gt;

&lt;p&gt;Every finding set is re-performed from the facts by a second route before it may be stored. A mismatch stores a refusal instead. The system would rather have a recorded refusal than a finding it cannot re-derive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqepwq08k600tme89rmdf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqepwq08k600tme89rmdf.webp" alt="Each finding names its bar; what clears no bar is named nowhere." width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Each finding names its bar; what clears no bar is named nowhere.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Drafts that cannot invent a number
&lt;/h2&gt;

&lt;p&gt;When the model enters — drafting the growth plan's narrative, the winback email copy, a budget note for the decaying ad set — it works under a figure contract. It is handed a figure sheet: the only numbers the draft may state, each carrying the artefact and field it resolves to. Its reply must include a claims list citing that sheet for every figure in the prose, and a verifier scans the prose for number tokens besides — a figure with no claim is a failure, and a draft with an invented or mismatched figure is refused whole and recorded as a refusal. Fluency is not evidence; the draft earns its numbers or it does not exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  A guardian, then a name
&lt;/h2&gt;

&lt;p&gt;A verified draft still goes nowhere. A separate guardian screen — a second, independent check in the pattern of Meta's own guardian-agent demonstrations — screens every draft on its way out. Then a named human approves, and the approval act is bound to the draft's content hash: edit one word after approval and the approval no longer covers the draft. Execution against real systems sits beyond that gate, deliberately out of the blueprint's scope: what is proven here is that nothing can reach the gate uncomputed, unverified, unscreened or unsigned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tier is a choice, with a price and a trade
&lt;/h2&gt;

&lt;p&gt;Muse Spark's API tiers make an honest design decision visible. At published prices, a standard call of a million tokens in and a million out costs $5.50; the contributor tier costs $0.30 for the same call — because contributor traffic may be used to improve Meta's products, and standard traffic is not. For an agent reading a business's real books, that is not a pricing footnote; it is a data decision. So the tier is explicit configuration, standard is the default, contributor must be chosen on purpose and is stamped on every cost-ledger record it touches, and every model call is written to that ledger with its tokens and computed cost, reconciling exactly. The hosted profile is wired and fails closed without a key — the sample runs here are the deterministic offline profile, and the blueprint says so rather than dressing them up as live calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The photograph that is not a vase
&lt;/h2&gt;

&lt;p&gt;A late addition shows the discipline generalising. Listing photographs enter through an intake that measures what code can measure — dimensions, format — and records what only a model can judge as the model's own artefact, labelled as judgement. In the sample catalogue: one product's image is 240 by 180 pixels against an 800 minimum; one product has no image at all; one file claims to be a JPEG and measures as a PNG; and one product, declared as a ceramic vase, is photographed as a candle duo — caught, and recorded as a model judgement with its basis, not quietly merged into the measured facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work with us
&lt;/h2&gt;

&lt;p&gt;If you run a small business on five disconnected tools — or you serve businesses that do — the governed version of this pattern is buildable: facts under contract, a ledger that re-performs itself, findings with stated thresholds, drafts that cannot invent numbers, and a named signature before anything leaves. Bring the problem through &lt;a href="https://trida.ai/intake" rel="noopener noreferrer"&gt;Trida AI's intake&lt;/a&gt; and we will scope what an operations agent over your stack should, and should not, be allowed to do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get the Code on GitHub&lt;/strong&gt;: &lt;a href="https://github.com/tridaai/blueprint-meta-muse-smb-commerce-operations" rel="noopener noreferrer"&gt;github.com/tridaai/blueprint-meta-muse-smb-commerce-operations&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tridaai</category>
      <category>blueprints</category>
    </item>
    <item>
      <title>Returns Resolution Agent Blueprint</title>
      <dc:creator>FDE at Trida AI</dc:creator>
      <pubDate>Mon, 02 Nov 2026 07:15:56 +0000</pubDate>
      <link>https://dev.to/tridaai/returns-resolution-agent-blueprint-4ebj</link>
      <guid>https://dev.to/tridaai/returns-resolution-agent-blueprint-4ebj</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://trida.ai/blog/returns-resolution-agent-blueprint" rel="noopener noreferrer"&gt;Trida AI&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Retail's AI demos tend to end at the checkout, which is a pity, because the expensive part of retail starts after the sale. A return is a small negotiation with money on both sides: the customer's refund, the retailer's recovered stock, a policy that has to be applied the same way on a Tuesday as on Black Friday, and a minority of requests that are not honest. Put a fluent model in charge of that negotiation and it will be charming, inconsistent, and occasionally generous with money that is not its own. This blueprint takes the opposite position, and holds it everywhere: the model talks, the code decides.&lt;/p&gt;

&lt;p&gt;This open blueprint, &lt;a href="https://github.com/tridaai/blueprint-agentcore-returns-resolution" rel="noopener noreferrer"&gt;the Returns Resolution Agent&lt;/a&gt;, runs on &lt;a href="https://aws.amazon.com/bedrock/agentcore/" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore&lt;/a&gt; and handles a return from request to receipt. Eligibility is computed from a versioned policy pack. The refund is a derived ledger. Disposition — where the returned item actually goes — is read off a matrix. Fraud is scored in code. Refunds move only through approval gates and an idempotent payments port. The model's single job is drafting the message the customer reads, and even that draft is verified figure by figure before it may exist. Follow one pair of trail boots through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day sixty of sixty
&lt;/h2&gt;

&lt;p&gt;The boots were delivered on August 27 and the return was requested on October 26 — the last day of the footwear window, exactly. The policy pack is data: windows by category (sixty days for apparel and footwear, ninety for gear, thirty for electronics), excluded categories, a final-sale exclusion, a bar on customer-damaged goods. The engine's decision records the pack version and the clauses that fired. The boots pass on the window's final day; their companion case in the sample feed, requested one day later, fails on the same clause. Both outcomes are graded, because an edge a system gets right by luck is an edge it will get wrong under pressure.&lt;/p&gt;

&lt;p&gt;Nothing about that decision involves the model. It also produces the paper trail an auditor wants: not "approved" but the clause list, replayable against the pack as it stood that day.&lt;/p&gt;

&lt;h2&gt;
  
  
  The refund is a ledger, not a sentence
&lt;/h2&gt;

&lt;p&gt;Eligibility says whether money moves. The computation says how much, and it is built like a ledger rather than an estimate: an item credit of $145.00 for the boots, a restocking deduction of $14.50 — the pack's ten percent for the customer's declared like-new condition — and a shipping deduction of zero, because the pack makes footwear returns free. Refund total: $130.50. Every line carries its derivation: the pinned input it came from, the rule, the policy reference. The whole computation is content-addressed and re-performed by a verifier that detects any altered line, because a refund figure nobody can re-derive is just a number someone typed.&lt;/p&gt;

&lt;p&gt;The ledger also shows its gap, in the open. The order carries no tender breakdown, so the allocation of the refund across payment methods is recorded as not computable, naming the missing input — the total is final, but where the money lands is not invented. That pattern recurs throughout the system: a missing input is a named state, never a quiet zero.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fssc43avv1k0ycfn18bv0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fssc43avv1k0ycfn18bv0.webp" alt="The refund computation for the boots: three derived lines, a final total, and one allocation honestly not computable." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The refund computation for the boots: three derived lines, a final total, and one allocation honestly not computable.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the boots go next is read off a matrix
&lt;/h2&gt;

&lt;p&gt;A refunded item is not finished; it has to go somewhere, and the somewhere is margin. Disposition is a versioned matrix over category, declared condition and age band — thirty-six cells, each naming a disposition from a closed vocabulary (restock, refurbish, outlet, liquidate, return to vendor, recycle, hold for inspection) and the channel it flows through. The boots, like-new and in season, read restock via the store. When a cell's channel is closed, the decision falls to the matrix's stated next cell and records the hop; a fact set that matches no cell comes back not decidable, naming the missing dimension, rather than defaulting to whatever someone usually does. A pair of used headphones in the same feed lands on hold for inspection, and the decision names what the inspection must establish — a functional test result and a data-wipe status — before anything else may happen to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A score can block the money
&lt;/h2&gt;

&lt;p&gt;Fraud is scored by a second pack, in code, from stored records only: return velocity, the share of a customer's orders that come back, wardrobing patterns, carrier weight against the product master's weight, payment-card churn. Each signal shows its inputs and its contribution; a signal without enough history says insufficient data instead of guessing. The sample feed's serial returner — eight orders, all eight returned, four payment cards — scores 95 and lands in the review band, which blocks the refund path outright.&lt;/p&gt;

&lt;p&gt;The block has exactly one door: a named fraud-review decision, with a coded outcome and a reason from the pack's vocabulary. In the sample, a reviewer clears the case with the reason recorded — and here is the detail that tells you the system is honest: the score artefact still reads 95, still in the review band. The decision lifted the block. It did not rewrite the arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two approvals, one receipt
&lt;/h2&gt;

&lt;p&gt;Refunds execute under thresholds the pack sets: automatic up to $50, one named approver above that, a second approver — a different person, enforced when the act is recorded — above $150. The boots' $130.50 takes one approval; the $189 case in the feed takes two, and its first execution attempt is refused, in so many words, because only one approver is on record. An approval pins the exact computation it approves, so approving yesterday's figures cannot authorise today's.&lt;/p&gt;

&lt;p&gt;Execution runs through the payments port under a mandatory idempotency key. Retrying the boots' refund with the same key returns the original receipt and the movement count stays at one; retrying under a new key is refused as a duplicate. The port's only implementation in the blueprint is a local double, labelled as such on every receipt it produces — it moves no real money, and says so. The gates, the idempotency contract and the failure handling are the deliverable; a production deployment binds its real payments provider behind the same port.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then the warehouse disagrees
&lt;/h2&gt;

&lt;p&gt;The boots come back, and the warehouse grades them used, not like-new. This is the moment most returns systems handle with an email and an argument. Here it triggers a cascade, all of it mechanical: the disposition is re-decided on the graded facts — restock becomes outlet — as a new linked decision that supersedes the declared one without editing it. The refund is re-priced under the used rate: $116.00 instead of $130.50. The $14.50 difference is recorded as an open recovery position, a receivable on the record — and no clawback is executed, because reaching back into a customer's account is a business decision with a human owner, not a cascade step. The customer summary is re-issued through the same figure verifier, quoting the new numbers and the reason they changed.&lt;/p&gt;

&lt;p&gt;Even the label keeps its own small honesty: on another case in the feed, the carrier charged $8.50 for a label against a $6.95 shipping deduction, and the $1.55 variance sits on the label's record as a variance — not absorbed, not rounded away, not hidden in a footnote.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr77hkocpg8g2skh53z3l.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr77hkocpg8g2skh53z3l.webp" alt="The grading cascade: the warehouse's used grade supersedes the declared like-new, the refund is re-priced, and the difference is held as a recorded position — not clawed back." width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The grading cascade: the warehouse's used grade supersedes the declared like-new, the refund is re-priced, and the difference is held as a recorded position — not clawed back.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The seams, stated
&lt;/h2&gt;

&lt;p&gt;The carrier, like payments, is a labelled local double — nothing really ships. Customer history is held as memory with limits: event-shaped summaries of closed cases, retrievable by exact customer identity only, every read written to an access log, and records past the retention window excluded and counted as expired rather than silently deleted. The AgentCore services around the agent — Gateway for the carrier tools, Policy for access, Memory for that history — are ports, live where wired and doubles where not, and the customer-facing words are the only place a model is allowed to speak, always downstream of the verifier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work with us
&lt;/h2&gt;

&lt;p&gt;If your returns operation runs on agent discretion — refunds negotiated chat by chat, disposition decided at the warehouse door, fraud spotted when someone happens to notice — the alternative is buildable: policy as data, refunds as ledgers, every block and approval a recorded act. Bring the problem through &lt;a href="https://trida.ai/intake" rel="noopener noreferrer"&gt;Trida AI's intake&lt;/a&gt; and we will scope what a returns agent inside your operation should, and should not, be allowed to touch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get the Code on GitHub&lt;/strong&gt;: &lt;a href="https://github.com/tridaai/blueprint-agentcore-returns-resolution" rel="noopener noreferrer"&gt;github.com/tridaai/blueprint-agentcore-returns-resolution&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tridaai</category>
      <category>blueprints</category>
    </item>
  </channel>
</rss>
