<?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: Zira</title>
    <description>The latest articles on DEV Community by Zira (@zira125).</description>
    <link>https://dev.to/zira125</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3821470%2Fe5f46063-a9b0-4a62-b149-0e284388c1ff.jpeg</url>
      <title>DEV Community: Zira</title>
      <link>https://dev.to/zira125</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zira125"/>
    <language>en</language>
    <item>
      <title>A Coding-Agent Sandbox Is Not a Boundary Until Enforcement Lives Outside It</title>
      <dc:creator>Zira</dc:creator>
      <pubDate>Wed, 23 Sep 2026 04:29:30 +0000</pubDate>
      <link>https://dev.to/zira125/a-coding-agent-sandbox-is-not-a-boundary-until-enforcement-lives-outside-it-5bmc</link>
      <guid>https://dev.to/zira125/a-coding-agent-sandbox-is-not-a-boundary-until-enforcement-lives-outside-it-5bmc</guid>
      <description>&lt;p&gt;A recent Codex sandbox report is a useful reminder for anyone building or operating coding agents: &lt;strong&gt;“read-only” is a capability setting, not proof that the host is safe.&lt;/strong&gt; The security boundary has to be enforced somewhere the agent cannot rewrite, impersonate, or influence from inside the sandbox.&lt;/p&gt;

&lt;p&gt;This is not a claim that every sandbox is broken. It is a design test: can the policy decision point be reached through the same process, memory, filesystem, or tool broker that the policy is supposed to constrain?&lt;/p&gt;

&lt;h2&gt;
  
  
  What was reported
&lt;/h2&gt;

&lt;p&gt;On September 20, BleepingComputer reported two Codex sandbox escapes disclosed by Accomplish AI researchers. The researchers said they reported both issues to OpenAI on August 12 and that both were fixed within eight days. That timeline and the technical details are researcher-reported, with the public incident covered independently by BleepingComputer.&lt;/p&gt;

&lt;p&gt;The first issue, called &lt;strong&gt;Heapjack&lt;/strong&gt; by the researchers, affected a JavaScript tool installed by Codex Desktop. Their write-up says the trusted and untrusted V8 contexts shared a Node process and heap. A token used to distinguish trusted code from untrusted code was therefore readable by the untrusted side. The proof of concept forged a request to the native parent process and launched an application outside the Codex process tree.&lt;/p&gt;

&lt;p&gt;The striking detail is the operating mode: the researchers say the path worked in &lt;code&gt;read-only&lt;/code&gt;, without an approval prompt. BleepingComputer described the attack scenario as opening someone else’s repository in Codex and asking a question about the code. That makes repository provenance part of the threat model, not just the commands the agent is asked to run.&lt;/p&gt;

&lt;p&gt;The second issue, called &lt;strong&gt;Overpatch&lt;/strong&gt;, involved the CLI patch tool. The researchers say a patch could name a path such as &lt;code&gt;/tmp&lt;/code&gt; to widen the tool’s calculated write permission, then use a symlink to reach a shell startup file outside the workspace. Again, the interesting failure is not “a bad path slipped through.” It is that attacker-controlled patch input influenced the mechanism that computed its own authority.&lt;/p&gt;

&lt;p&gt;According to the researchers and BleepingComputer, the fixes were Codex Desktop build &lt;code&gt;26.818.21641&lt;/code&gt; for Heapjack and Codex CLI &lt;code&gt;0.149.0&lt;/code&gt; for Overpatch. Treat those as minimums from the report, not as a substitute for checking the current release and your organization’s advisories.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shared design failure
&lt;/h2&gt;

&lt;p&gt;Both paths have the same shape:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The agent supplied input to a tool.&lt;/li&gt;
&lt;li&gt;The tool used that input while deciding what the agent was allowed to do.&lt;/li&gt;
&lt;li&gt;The enforcement state was reachable from the same trust domain as the untrusted code.&lt;/li&gt;
&lt;li&gt;A successful request crossed into a more privileged process or path.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is why “the model cannot directly call &lt;code&gt;open&lt;/code&gt;” or “the shell cannot write &lt;code&gt;$HOME&lt;/code&gt;” is not enough. The real questions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can an untrusted process inspect the authority token, broker pipe, or shared memory used by the trusted side?&lt;/li&gt;
&lt;li&gt;Can a path alias, symlink, mount, archive extraction, or parent-directory calculation change the meaning of “inside the workspace”?&lt;/li&gt;
&lt;li&gt;Can a tool outside the sandbox be induced to perform the action on the agent’s behalf?&lt;/li&gt;
&lt;li&gt;Does a restart preserve a malicious config, hook, plugin, or shell profile?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OpenAI’s current security guidance describes sandboxing and approvals as separate layers: the sandbox limits what commands can technically do, while approval policy controls when Codex asks before acting. The same documentation warns that settings such as full access deliberately widen the trust boundary. That is good operational guidance, but it does not remove the need to test the implementation boundary itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical test matrix for agent builders
&lt;/h2&gt;

&lt;p&gt;You do not need an exploit reproduction to catch many classes of boundary mistake. Add adversarial tests around the policy decision point.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Test repository provenance
&lt;/h3&gt;

&lt;p&gt;Run an untrusted repository in the strictest mode and ask the agent to inspect it. Verify that it cannot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create or modify files outside the intended root;&lt;/li&gt;
&lt;li&gt;launch a host application;&lt;/li&gt;
&lt;li&gt;connect to a Docker, SSH, credential, or other privileged Unix socket;&lt;/li&gt;
&lt;li&gt;alter global agent configuration; or&lt;/li&gt;
&lt;li&gt;cause a trusted helper to perform any of those actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Record both the agent-visible result and host-side evidence. A clean chat response is not an audit record.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Test path semantics, not only path strings
&lt;/h3&gt;

&lt;p&gt;Exercise symlinks, &lt;code&gt;..&lt;/code&gt; traversal, hard links where applicable, bind mounts, archive extraction, temporary directories, case folding, and platform-specific path forms. Test the parent directory and the final target separately. If a tool calculates permission from a path supplied by the agent, make that calculation happen in a process that cannot be modified by the agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Separate policy from execution
&lt;/h3&gt;

&lt;p&gt;The component deciding “allowed” should not share mutable authority state with the component executing untrusted code. A stronger pattern is an outer VM or OS boundary, a narrow host broker, and credentials that never enter the guest. The broker should validate an explicit capability request against policy; it should not accept a token or path whose meaning the guest can redefine.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Test the recovery boundary
&lt;/h3&gt;

&lt;p&gt;After a failed run, rebuild or reset the environment and check global config, shell startup files, plugin registrations, temporary mounts, and cached credentials. A sandbox that blocks the first action but leaves a persistent hook behind has not recovered cleanly.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Treat egress as its own control
&lt;/h3&gt;

&lt;p&gt;Filesystem isolation does not stop data exfiltration if the agent can reach the network. Start with no network, then add narrowly scoped destinations through a policy the guest cannot disable. Keep real credentials outside the guest where possible, and assume anything placed inside an untrusted runtime can be read by code running there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I would use
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The agent may be untrusted. The policy engine must be more trusted than the agent.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If both live in one process, share a heap, share a mutable config, or allow attacker-controlled input to define the permission calculation, call it a defense-in-depth control rather than a hard security boundary.&lt;/p&gt;

&lt;p&gt;The useful review question is not “does this feature say sandbox?” It is: &lt;strong&gt;what independent mechanism still says no when the agent tries to make the sandbox say yes?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and evidence
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Researcher write-up: &lt;a href="https://www.accomplish.ai/blog/escaping-the-openai-codex-sandbox-twice/" rel="noopener noreferrer"&gt;Escaping the OpenAI Codex sandbox, twice&lt;/a&gt; (primary technical disclosure; self-reported)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.bleepingcomputer.com/news/security/researchers-escape-openai-codex-sandbox-to-run-commands-on-host/" rel="noopener noreferrer"&gt;BleepingComputer coverage&lt;/a&gt; (independent technical reporting)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.openai.com/codex/agent-approvals-security" rel="noopener noreferrer"&gt;OpenAI Codex security guidance&lt;/a&gt; (official product documentation; describes current sandbox, approval, network, and VM guidance)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>ai</category>
      <category>agents</category>
      <category>security</category>
    </item>
    <item>
      <title>Visual Studio 2026 BYOK Agent Mode: 6 Tests Before You Trust It</title>
      <dc:creator>Zira</dc:creator>
      <pubDate>Wed, 16 Sep 2026 07:10:00 +0000</pubDate>
      <link>https://dev.to/zira125/visual-studio-2026-byok-agent-mode-6-tests-before-you-trust-it-3cj3</link>
      <guid>https://dev.to/zira125/visual-studio-2026-byok-agent-mode-6-tests-before-you-trust-it-3cj3</guid>
      <description>&lt;p&gt;Microsoft's Visual Studio 2026 September update makes a useful shift in AI-assisted development: the model is becoming a replaceable deployment choice instead of a fixed IDE dependency.&lt;/p&gt;

&lt;p&gt;The important part is not simply that Visual Studio can connect to more providers. It is that the new Agent (Preview) changes the runtime contract around those providers.&lt;/p&gt;

&lt;p&gt;Microsoft's release notes say the September 18.10 update adds preview BYOK support across Community, Professional, and Enterprise editions. The supported paths include Microsoft Foundry, OpenAI, Anthropic, Ollama, and custom endpoints for OpenAI and Ollama. The same notes also call out a breaking change: BYOK now works with the new Agent (Preview), while the earlier BYOK experience in the previous Ask and Agent modes is no longer supported. Ollama models added in an earlier 18.10 Insider build may need to be added again.&lt;/p&gt;

&lt;p&gt;That is a meaningful capability increase, but it is also a reminder that an agent is a control plane, not just a chat box. Before giving a new provider access to a real repository, I would test these six boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Test the capability contract, not just the connection
&lt;/h2&gt;

&lt;p&gt;A provider can connect successfully and still lack important Agent Mode capabilities. Microsoft says unsupported features are marked clearly instead of failing silently. That is better than an invisible fallback, but your workflow should still treat capability discovery as a first-class step.&lt;/p&gt;

&lt;p&gt;Create a small smoke repository that exercises the exact actions you expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;read files across multiple directories&lt;/li&gt;
&lt;li&gt;propose and apply a change&lt;/li&gt;
&lt;li&gt;run the test suite&lt;/li&gt;
&lt;li&gt;inspect a failing test and retry&lt;/li&gt;
&lt;li&gt;create a patch or commit&lt;/li&gt;
&lt;li&gt;explain which tools were used&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Record the result per model and endpoint. A green connection check is not evidence that the provider can perform the full workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Separate provider identity from developer identity
&lt;/h2&gt;

&lt;p&gt;BYOK changes where inference happens. It does not automatically answer who is allowed to use the agent, which organization owns the key, or which repository the agent may access.&lt;/p&gt;

&lt;p&gt;Use separate credentials for local experiments, team development, and automation. Avoid putting a long-lived provider key in a repository, shell history, or a shared machine image. If the provider supports project-level keys or spend limits, use them. The useful audit record is not just “the agent ran”; it is “which principal, endpoint, model, repository, and tool policy authorized this run?”&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Make tool permissions explicit
&lt;/h2&gt;

&lt;p&gt;Agent Mode can be valuable precisely because it can do more than generate text. That also makes broad permissions dangerous. Start with a repository-specific working directory and a deny-by-default policy for unrelated paths, network access, package installation, and credential files.&lt;/p&gt;

&lt;p&gt;A simple policy sketch might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./sample-repo&lt;/span&gt;
  &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;read:src/**&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;write:src/**&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;run:dotnet test&lt;/span&gt;
  &lt;span class="na"&gt;deny&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;read:.env*&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;read:**/*credential*&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;write:.github/workflows/**&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;network:*&lt;/span&gt;
  &lt;span class="na"&gt;approval_required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;package_install&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;git_push&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;database_migration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact format will vary by harness. The point is to make the boundary reviewable before the model sees the repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Preserve evidence outside the agent's control
&lt;/h2&gt;

&lt;p&gt;If the agent edits the same files that contain its own explanation, those files are not a complete audit trail. Capture the request, selected endpoint, model identifier, tool calls, diffs, test output, approvals, and final status in a location the agent cannot rewrite retroactively.&lt;/p&gt;

&lt;p&gt;Do not log secrets or full prompts that contain credentials. A useful minimum is a run ID, repository commit, policy version, provider, model, timestamps, tool results, and hashes of the resulting patch.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Test side effects and interruption
&lt;/h2&gt;

&lt;p&gt;Run the agent against a disposable branch and interrupt it at several points: during a file edit, during a test run, and after a commit but before the final response.&lt;/p&gt;

&lt;p&gt;Then answer two different questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Can the execution resume without losing state?&lt;/li&gt;
&lt;li&gt;Can the workflow prove whether a side effect already happened?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those are not the same failure. A restart may recover the conversation while still repeating a commit, package install, migration, or external request. Use checkpoints and idempotency keys where the workflow can create durable side effects.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Treat preview upgrades as contract changes
&lt;/h2&gt;

&lt;p&gt;The release notes explicitly describe the new BYOK path as preview and document breaking changes between Insider builds. That is enough reason to pin the IDE and provider configuration for repeatable work, keep a known-good fallback, and retest after upgrades.&lt;/p&gt;

&lt;p&gt;For Ollama in particular, Microsoft says models added in an earlier 18.10 Insider build may need to be added again in the next Insider build. That is a small migration detail, but it is exactly the sort of detail that turns a “model outage” into a confusing afternoon if it is not in the upgrade checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical takeaway
&lt;/h2&gt;

&lt;p&gt;Visual Studio's BYOK Agent Preview is interesting because it makes model choice more flexible. The engineering challenge is making that flexibility observable and reversible.&lt;/p&gt;

&lt;p&gt;Before adopting it for a production repository, keep a small compatibility matrix:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider/model&lt;/th&gt;
&lt;th&gt;Agent capabilities&lt;/th&gt;
&lt;th&gt;Tool policy&lt;/th&gt;
&lt;th&gt;Test result&lt;/th&gt;
&lt;th&gt;Rollback path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;endpoint A&lt;/td&gt;
&lt;td&gt;pass/fail per action&lt;/td&gt;
&lt;td&gt;policy v3&lt;/td&gt;
&lt;td&gt;commit + tests&lt;/td&gt;
&lt;td&gt;endpoint B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;endpoint B&lt;/td&gt;
&lt;td&gt;pass/fail per action&lt;/td&gt;
&lt;td&gt;policy v3&lt;/td&gt;
&lt;td&gt;interrupted run&lt;/td&gt;
&lt;td&gt;pinned IDE build&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is a vendor-documented preview, not independent proof of reliability or productivity. The right first experiment is therefore not a benchmark screenshot. It is a bounded repository, explicit permissions, an external audit trail, and a recovery test you can repeat after the next update.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://learn.microsoft.com/en-us/visualstudio/releases/2026/release-notes" rel="noopener noreferrer"&gt;Visual Studio 2026 release notes&lt;/a&gt;, including the September 8, 2026 18.10 update and the September 15, 2026 18.10.1 patch.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>Codex CLI 0.153: Remote Plugins Make Supply-Chain Policy Part of Your Agent Runtime</title>
      <dc:creator>Zira</dc:creator>
      <pubDate>Thu, 03 Sep 2026 04:25:25 +0000</pubDate>
      <link>https://dev.to/zira125/codex-cli-0153-remote-plugins-make-supply-chain-policy-part-of-your-agent-runtime-1pn2</link>
      <guid>https://dev.to/zira125/codex-cli-0153-remote-plugins-make-supply-chain-policy-part-of-your-agent-runtime-1pn2</guid>
      <description>&lt;p&gt;Codex CLI 0.153.0 is not just a terminal UI update. It adds a plugin command that can list, install, and remove plugins from remote marketplaces, while also improving how sessions recover after an app-server disconnect.&lt;/p&gt;

&lt;p&gt;That combination changes the operational question from “Can the agent call a tool?” to “Which extension code is allowed to enter this runtime, and what state survives when the runtime reconnects?”&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in Codex CLI 0.153
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/openai/codex/releases/tag/rust-v0.153.0" rel="noopener noreferrer"&gt;official 0.153.0 release&lt;/a&gt; adds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A plugin CLI for listing, installing, and removing plugins from remote marketplaces.&lt;/li&gt;
&lt;li&gt;Automatic TUI reconnection after an external app-server connection drop, preserving drafts and transcripts while pausing uncertain or queued submissions for review.&lt;/li&gt;
&lt;li&gt;Guardian review history that survives compaction, restarts, and user-created forks, within rollback boundaries and isolated subagent history.&lt;/li&gt;
&lt;li&gt;More complete TUI history, including patches, background-terminal input, and individual completed commands.&lt;/li&gt;
&lt;li&gt;A tui.auto_recap = false setting for teams that prefer manual /recap control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The release notes are the authoritative source for these features. AI/TLDR’s &lt;a href="https://ai-tldr.dev/releases/openai-codex-cli-0-153/" rel="noopener noreferrer"&gt;release summary&lt;/a&gt; is useful for discovery, but it is a secondary summary, not independent performance evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remote plugins are a new trust boundary
&lt;/h2&gt;

&lt;p&gt;A marketplace client reduces the friction of extending an agent. It also makes installation provenance part of the runtime’s security model.&lt;/p&gt;

&lt;p&gt;Before allowing a plugin into a development environment, record at least:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The marketplace and exact source URL.&lt;/li&gt;
&lt;li&gt;The plugin version and immutable commit or digest, if available.&lt;/li&gt;
&lt;li&gt;The tools, filesystem paths, network destinations, and credentials it can reach.&lt;/li&gt;
&lt;li&gt;The review status and the person or policy that approved it.&lt;/li&gt;
&lt;li&gt;A removal and rollback path that does not depend on the plugin itself.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A name-based allowlist is not enough. Two versions of the same plugin can have different behavior, and a plugin that only appears to “format code” may still be able to read files or invoke a shell through its host integration.&lt;/p&gt;

&lt;p&gt;A minimal installation record should preserve the plugin name, marketplace URL, exact version, immutable digest, effective capabilities, approver, and expiry. Store that record outside the plugin so removal and rollback do not depend on the plugin itself.&lt;/p&gt;

&lt;p&gt;The digest and capability fields are the important parts of this policy shape. They are recommendations, not claims about the Codex marketplace schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconnect is not the same as resume
&lt;/h2&gt;

&lt;p&gt;The new reconnect behavior is valuable because a dropped app-server connection no longer necessarily destroys the draft or transcript. But preserving state does not prove that an interrupted tool submission did not reach its destination.&lt;/p&gt;

&lt;p&gt;For every side-effecting tool call, keep execution state separate from delivery state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prepared:&lt;/strong&gt; arguments validated, no external call started.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In flight:&lt;/strong&gt; the request may have reached the destination.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accepted:&lt;/strong&gt; the destination returned a durable identifier or equivalent evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown:&lt;/strong&gt; the client lost the response after sending.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconciled:&lt;/strong&gt; a separate read confirmed the external state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After reconnect, an “unknown” call should not be blindly replayed. Use an idempotency key where the destination supports one, or reconcile by querying an external identifier before retrying. This is the difference between recovering the agent session and recovering the real-world side effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical upgrade checklist
&lt;/h2&gt;

&lt;p&gt;If you are upgrading a long-running coding-agent environment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pin the CLI version and capture the release commit.&lt;/li&gt;
&lt;li&gt;Test plugin installation in a disposable workspace first.&lt;/li&gt;
&lt;li&gt;Inspect the plugin’s effective filesystem, network, and credential reach.&lt;/li&gt;
&lt;li&gt;Require review for new plugin sources and record approval with an expiry.&lt;/li&gt;
&lt;li&gt;Kill the app-server during a draft and during a tool call.&lt;/li&gt;
&lt;li&gt;Confirm that drafts and transcripts return after reconnect.&lt;/li&gt;
&lt;li&gt;Confirm that queued or uncertain submissions remain paused.&lt;/li&gt;
&lt;li&gt;Reconcile external state before retrying any side effect.&lt;/li&gt;
&lt;li&gt;Verify that rollback removes the plugin and its persisted state.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The release improves the recovery path, but it does not eliminate the need for capability boundaries, idempotency, or recovery testing. Those remain properties of the surrounding deployment and the tool destinations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Codex CLI 0.153 makes two parts of agent infrastructure more explicit: extension provenance and session recovery. Treat remote plugins as code entering a privileged runtime, and treat reconnect as state recovery rather than proof that every external action is safe to repeat.&lt;/p&gt;

&lt;p&gt;For teams that need an always-on place to run an agent, &lt;a href="https://ampere.sh" rel="noopener noreferrer"&gt;Ampere&lt;/a&gt; can be a contextual hosting option, but hosting does not replace plugin review, credential scoping, idempotency, or restore testing.&lt;/p&gt;

&lt;p&gt;Primary source: &lt;a href="https://github.com/openai/codex/releases/tag/rust-v0.153.0" rel="noopener noreferrer"&gt;OpenAI Codex CLI 0.153.0 release notes&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devtools</category>
      <category>ai</category>
      <category>mcp</category>
      <category>agents</category>
    </item>
    <item>
      <title>Your AI Coding Agent Can Edit Compose UI. Can It Verify the Result?</title>
      <dc:creator>Zira</dc:creator>
      <pubDate>Wed, 02 Sep 2026 22:18:49 +0000</pubDate>
      <link>https://dev.to/zira125/your-ai-coding-agent-can-edit-compose-ui-can-it-verify-the-result-1ad4</link>
      <guid>https://dev.to/zira125/your-ai-coding-agent-can-edit-compose-ui-can-it-verify-the-result-1ad4</guid>
      <description>&lt;p&gt;AI coding agents are good at producing UI code and surprisingly bad at proving that the UI actually works. A compile pass is not the same as a rendered screen, a correct semantic tree, or a click that reaches the intended handler.&lt;/p&gt;

&lt;p&gt;Compose Multiplatform 1.12.0 adds an experimental MCP server to Compose Hot Reload. It gives an AI coding agent a feedback path into a running Compose application: trigger a reload, take a screenshot, inspect the semantic tree, simulate clicks and text input, and read application logs.&lt;/p&gt;

&lt;p&gt;That changes the useful question from “Can the agent write this composable?” to “Can the agent observe the result of its edit and detect a broken interaction?”&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Compose Hot Reload MCP server exposes
&lt;/h2&gt;

&lt;p&gt;According to JetBrains’ &lt;a href="https://blog.jetbrains.com/kotlin/2026/08/compose-multiplatform-1-12-0/" rel="noopener noreferrer"&gt;release announcement&lt;/a&gt;, the experimental server connects an agent to a running app and supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;triggering reloads after an edit&lt;/li&gt;
&lt;li&gt;taking screenshots of the rendered UI&lt;/li&gt;
&lt;li&gt;inspecting the semantic tree&lt;/li&gt;
&lt;li&gt;simulating clicks and text input&lt;/li&gt;
&lt;li&gt;reading application logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/JetBrains/compose-hot-reload#mcp-server-for-ai-agents" rel="noopener noreferrer"&gt;Compose Hot Reload documentation&lt;/a&gt; contains the connection details and the current tool list. The &lt;a href="https://github.com/JetBrains/compose-multiplatform/releases/tag/v1.12.0" rel="noopener noreferrer"&gt;Compose Multiplatform 1.12.0 release notes&lt;/a&gt; are the versioned reference for the feature.&lt;/p&gt;

&lt;p&gt;This is an observation loop, not an automatic correctness proof. A screenshot can show that a button is visible, while the semantic tree and logs can provide stronger evidence about labels, roles, and runtime failures. None of those alone proves that the feature satisfies the product requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical agent verification loop
&lt;/h2&gt;

&lt;p&gt;A useful harness should make the agent follow an explicit sequence rather than letting it stop after compilation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Edit one bounded change.&lt;/strong&gt; Keep the diff small enough that a failed observation has a plausible cause.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reload the running app.&lt;/strong&gt; Record whether reload completed or timed out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the semantic tree.&lt;/strong&gt; Assert that required nodes exist with the expected labels or roles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capture a screenshot.&lt;/strong&gt; Use it to catch layout regressions, missing content, or an unexpected screen state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exercise one critical interaction.&lt;/strong&gt; Simulate the click or text entry that matters for the changed path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read logs after the interaction.&lt;/strong&gt; Treat new exceptions or error-level entries as a failed run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store evidence with the change.&lt;/strong&gt; Keep the reload result, assertions, screenshot reference, and relevant logs attached to the agent run.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important design choice is to keep these as separate checks. “The screenshot looks right” should not silently substitute for “the semantic node exists,” and “the reload succeeded” should not substitute for “the interaction produced the expected state transition.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this helps most
&lt;/h2&gt;

&lt;p&gt;The MCP server is especially useful for UI changes with a short, deterministic path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a form field must accept input and update state&lt;/li&gt;
&lt;li&gt;a button must become enabled after validation&lt;/li&gt;
&lt;li&gt;a navigation action must render a specific destination&lt;/li&gt;
&lt;li&gt;a loading or error state must appear under a controlled condition&lt;/li&gt;
&lt;li&gt;a regression must be reproduced from a known starting state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each path, define an observable contract. For example: after entering a valid email and clicking &lt;strong&gt;Continue&lt;/strong&gt;, the semantic tree contains the next-screen heading, the log contains no new exception, and the screenshot shows the expected state.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tradeoffs and the safety boundary
&lt;/h2&gt;

&lt;p&gt;This is an experimental developer tool, so it should be treated as a test harness rather than a production control surface. An agent that can simulate input and inspect a running app has meaningful capabilities. Run it against a disposable or tightly scoped environment, avoid real credentials and production data, and keep the available actions limited to the application under test.&lt;/p&gt;

&lt;p&gt;There is also a determinism problem. Screenshots and logs can vary with timing, platform, fonts, network state, and background work. Prefer assertions over stable semantic properties and explicit state transitions. Use screenshots as supporting evidence, not as the only oracle.&lt;/p&gt;

&lt;p&gt;Finally, separate execution evidence from product correctness. The tool can show what happened in the running app; it cannot decide whether the feature is desirable, safe to ship, or compliant with a business requirement. Those remain separate review gates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;Compose Hot Reload’s experimental MCP server is valuable because it closes part of the loop between an AI coding agent and the UI it changes. The practical benefit is not that the agent becomes trustworthy by default. It is that the harness can require observable evidence before accepting a UI edit.&lt;/p&gt;

&lt;p&gt;The strongest workflow is therefore: small diff, reload, semantic assertions, targeted interaction, log check, and persisted evidence. Give the agent a way to see the result, then make the acceptance contract stricter than “it compiled.”&lt;/p&gt;

</description>
      <category>devtools</category>
      <category>ai</category>
      <category>agents</category>
      <category>mcp</category>
    </item>
    <item>
      <title>AI Coding Agent Security: Why a Git Folder Can Run Code Before Approval</title>
      <dc:creator>Zira</dc:creator>
      <pubDate>Wed, 02 Sep 2026 11:00:09 +0000</pubDate>
      <link>https://dev.to/zira125/ai-coding-agent-security-why-a-git-folder-can-run-code-before-approval-5c07</link>
      <guid>https://dev.to/zira125/ai-coding-agent-security-why-a-git-folder-can-run-code-before-approval-5c07</guid>
      <description>&lt;p&gt;A new class of AI coding agent security bug has a surprisingly ordinary entry point: Git itself.&lt;/p&gt;

&lt;p&gt;Manifold Security calls the issue GitSpawn. The core problem is not that an agent misunderstood a prompt. It is that an agent ran a normal Git command while gathering repository context, and Git honored an executable setting stored inside the repository's own .git/config.&lt;/p&gt;

&lt;p&gt;That turns “open this project” into a potentially privileged operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mechanism: context gathering becomes code execution
&lt;/h2&gt;

&lt;p&gt;Many CLI coding agents inspect a repository as soon as they start. They may run commands such as:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git status --porcelain=2 --branch
git diff --name-only HEAD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;These are ordinary commands, but Git may refresh its index before returning the result. Git's core.fsmonitor setting can name a helper program to run during that refresh. The setting is read from the repository-local .git/config file.&lt;/p&gt;

&lt;p&gt;A project delivered as a complete directory, ZIP archive, shared-drive folder, USB copy, or sync-folder snapshot can therefore carry a Git configuration that names a program. When the agent runs Git, the helper can execute with the developer's operating-system privileges.&lt;/p&gt;

&lt;p&gt;The important boundary is timing. Manifold reports that, in the affected flows it tested, execution happened during background context gathering, before a workspace-trust prompt, before an approval prompt, and in some cases before authentication. The model does not need to generate a dangerous command, and the user may not see a prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the disclosure actually found
&lt;/h2&gt;

&lt;p&gt;This is a researcher disclosure, not an independent prevalence study. Manifold reports eight findings across seven agents and says four remained unpatched at publication. Its September 1 update says OpenAI Codex and Cursor were also affected and had since been patched.&lt;/p&gt;

&lt;p&gt;The report lists these statuses at publication:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code's core.fsmonitor path: fixed in 2.1.196.&lt;/li&gt;
&lt;li&gt;Goose: fixed in 1.44.0, with CVE-2026-72718.&lt;/li&gt;
&lt;li&gt;OpenAI Codex and Cursor: patched, according to the update.&lt;/li&gt;
&lt;li&gt;Hermes Agent: still unpatched in the tested 0.21.0 release, with CVE-2026-71963.&lt;/li&gt;
&lt;li&gt;Qwen Code: still unpatched in the tested 0.22.3 release.&lt;/li&gt;
&lt;li&gt;Grok Build: still unpatched in the tested 1.0.13 release.&lt;/li&gt;
&lt;li&gt;Claude Code's separate ultrareview path: reported as still unpatched in the tested 2.1.252 release.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat those version claims as time-sensitive. Check the vendor's current release notes and advisory before deciding whether a version is safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a sandbox may not save you
&lt;/h2&gt;

&lt;p&gt;A sandbox around the model's tool calls is not automatically a sandbox around every subprocess the agent launches. In this case, the dangerous process is Git, which the agent invokes for its own bookkeeping. If the product does not sanitize repository configuration for that subprocess, the command can run outside the model-facing approval path.&lt;/p&gt;

&lt;p&gt;That is a useful design lesson for agent harnesses: permission checks must cover the complete execution graph, including startup probes, Git helpers, formatters, language servers, hooks, plugins, MCP servers, and other “read-only” context tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  A safer intake workflow
&lt;/h2&gt;

&lt;p&gt;Before opening an unfamiliar directory with an AI coding agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prefer cloning from a trusted remote over accepting a complete project folder. A normal clone does not carry the remote repository's original .git/config.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inspect the local Git configuration without launching the agent:&lt;/p&gt;

&lt;p&gt;sed -n '1,200p' .git/config&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Treat any Git setting that names an executable as untrusted code. The reported proof of concept uses core.fsmonitor, but the broader rule is more useful than memorizing one key.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a disposable VM or tightly scoped container for untrusted project directories. Keep SSH keys, cloud credentials, and production tokens out of that environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Update the agent and verify the exact fixed version. “The agent has an approval prompt” is not enough if startup context gathering happens before the prompt.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For agent authors, the mitigation is equally concrete: pass safe Git configuration explicitly for background calls, for example git -c core.fsmonitor=false status, and review every subprocess used before trust and authorization decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The broader takeaway
&lt;/h2&gt;

&lt;p&gt;AI coding agent security is partly a model problem, but it is also a plumbing problem. The model can be perfectly aligned while the harness exposes a trusted developer process to untrusted repository metadata.&lt;/p&gt;

&lt;p&gt;The right review question is not only “What commands can the agent propose?” It is also “What runs automatically before the user has approved anything?” Audit that startup path, test it with hostile-but-non-destructive fixtures, and make execution, approval, and sandbox boundaries line up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sources and evidence provenance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.manifold.security/blog/ai-coding-agents-git-hijack" rel="noopener noreferrer"&gt;Manifold Security: Coding Agents Hijacked by a Git Call&lt;/a&gt; — primary researcher disclosure; version and patch status are the researcher's reported findings.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai-tldr.dev/releases/manifold-gitspawn/" rel="noopener noreferrer"&gt;AI/TLDR summary of GitSpawn&lt;/a&gt; — secondary tracker summary, cross-checked against the primary report.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/docs/git-config#Documentation/git-config.txt-corefsmonitor" rel="noopener noreferrer"&gt;Git documentation for core.fsmonitor&lt;/a&gt; — upstream reference for the Git configuration behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article intentionally avoids reproducing a weaponized repository or payload. The goal is to make the boundary visible and give developers a safer intake and testing workflow.&lt;/p&gt;

</description>
      <category>devtools</category>
      <category>security</category>
      <category>agents</category>
      <category>ai</category>
    </item>
    <item>
      <title>Codex CLI 0.152: The Small Release That Changes Agent Control</title>
      <dc:creator>Zira</dc:creator>
      <pubDate>Wed, 02 Sep 2026 05:00:09 +0000</pubDate>
      <link>https://dev.to/zira125/codex-cli-0152-the-small-release-that-changes-agent-control-51l4</link>
      <guid>https://dev.to/zira125/codex-cli-0152-the-small-release-that-changes-agent-control-51l4</guid>
      <description>&lt;p&gt;OpenAI’s Codex CLI 0.152.0 release is easy to read as a routine feature update. The more useful reading is operational: it changes how a coding agent plans, consumes MCP output, handles long-running commands, and protects credentials during cloud-task requests.\n\nThis is not a benchmark claim. It is a release-level change list, and the practical impact depends on how you run Codex.\n\n## 1. Planning is now opt-in\n\nThe release disables the &lt;code&gt;update_plan&lt;/code&gt; tool by default. If your workflow depends on explicit plans, enable it with:\n\n    tools.update_plan.enabled = true\n\nThat is a small configuration change with a large workflow implication. A plan is not the same thing as execution state. If you use plans for review, handoff, or recovery after interruption, store the important state somewhere your process can recover rather than relying on a transient conversation view.\n\n## 2. MCP output gets a per-tool budget\n\nCodex 0.152.0 adds an &lt;code&gt;output_token_limit&lt;/code&gt; setting for individual MCP tools, and the release notes say truncation is consistent across session resumes. This is useful because one verbose tool can otherwise crowd out the context needed for the actual coding task.\n\nA practical starting point is to set limits by tool role:\n\n- Small limits for status and metadata tools\n- Moderate limits for search and diagnostics\n- Larger, explicitly reviewed limits for logs or generated reports\n\nThe tradeoff is obvious: truncation controls context pressure, but it can also hide the line that explains a failure. Tool output should therefore carry a cursor, artifact link, or continuation mechanism instead of silently dropping information.\n\n## 3. Long commands need an explicit deadline\n\nApp-server clients can configure &lt;code&gt;thread/shellCommand&lt;/code&gt; timeouts, including deadlines longer than one hour. That helps with builds, test suites, and migrations, but a longer timeout is not a cancellation contract. A production runner still needs progress reporting, cancellation behavior, retry rules, and a durable record of whether the command actually completed.\n\n## 4. Credential protection is part of the agent feature set\n\nThe release notes also say cloud task requests reject untrusted backend URLs and disable redirects. That matters because redirects are not just a networking detail when a task can access saved credentials. A safer request path needs an explicit origin policy, and the policy should be tested with redirects, DNS changes, expired credentials, and resumed sessions.\n\n## Where a hosted runtime fits\n\nIf Codex is part of an always-on remote workflow, a hosted runtime such as &lt;a href="https://ampere.sh" rel="noopener noreferrer"&gt;Ampere&lt;/a&gt; can be a convenient place to keep the process and its surrounding automation available. That does not remove the need for state contracts, scoped credentials, isolation, idempotency, or recovery tests. Hosting solves a runtime-placement problem; it does not automatically solve the control-plane problem.\n\n## A practical upgrade checklist\n\nAfter upgrading to 0.152.0, I would test four things before trusting a long-running workflow:\n\n1. Start a task with planning enabled and confirm the setting survives a new session.\n2. Send oversized output through each important MCP tool and verify truncation is visible and recoverable.\n3. Interrupt and resume a long shell command, checking whether execution state is unambiguous.\n4. Attempt a cloud-task redirect to an untrusted origin and confirm it is rejected without exposing credentials.\n\nCodex CLI 0.152.0 is therefore less about a flashy new capability and more about making agent behavior bounded and inspectable. Those defaults are worth reviewing before treating an upgrade as a drop-in replacement.\n\n### Sources\n\n- &lt;a href="https://github.com/openai/codex/releases/tag/rust-v0.152.0" rel="noopener noreferrer"&gt;OpenAI Codex CLI 0.152.0 release notes&lt;/a&gt;\n- &lt;a href="https://developers.openai.com/codex/changelog" rel="noopener noreferrer"&gt;OpenAI Codex CLI changelog&lt;/a&gt;\n- &lt;a href="https://ai-tldr.dev/releases/openai-codex-cli-0-152/" rel="noopener noreferrer"&gt;AI/TLDR release summary&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devtools</category>
    </item>
    <item>
      <title>Your Agent Gateway Needs Failure-Domain Tests, Not Just a Health Check</title>
      <dc:creator>Zira</dc:creator>
      <pubDate>Mon, 24 Aug 2026 01:41:56 +0000</pubDate>
      <link>https://dev.to/zira125/your-agent-gateway-needs-failure-domain-tests-not-just-a-health-check-54fc</link>
      <guid>https://dev.to/zira125/your-agent-gateway-needs-failure-domain-tests-not-just-a-health-check-54fc</guid>
      <description>&lt;p&gt;A green health check can coexist with a broken agent.&lt;/p&gt;

&lt;p&gt;If you are trying to keep an OpenClaw agent available without owning the server setup, Ampere is a free hosting option to evaluate: &lt;a href="https://ampere.sh" rel="noopener noreferrer"&gt;https://ampere.sh&lt;/a&gt;. It can reduce the infrastructure work, but it does not replace durable state, scoped credentials, isolation, or recovery testing.&lt;/p&gt;

&lt;p&gt;The process may be listening, the dashboard may say "healthy", and yet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a second inbound message disappears before it enters a queue;&lt;/li&gt;
&lt;li&gt;a gateway restart loses a suspended tool call;&lt;/li&gt;
&lt;li&gt;a background run reaches "completed" but its announcement never arrives.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are not one availability problem. They are three different recovery contracts.&lt;/p&gt;

&lt;p&gt;A useful field report on &lt;a href="https://github.com/openclaw/openclaw/issues/128067" rel="noopener noreferrer"&gt;OpenClaw issue #128067&lt;/a&gt; describes all three symptoms in one deployment. This is practitioner-reported evidence, not an independent benchmark, but it suggests a practical test plan: stop asking whether the gateway is alive and test whether each failure domain preserves its contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model the states separately
&lt;/h2&gt;

&lt;p&gt;Do not collapse everything into a single &lt;code&gt;done&lt;/code&gt; boolean. Keep at least these states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;accepted&lt;/code&gt;: the ingress boundary assigned a durable request ID;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;queued&lt;/code&gt;: a queue record exists and can be recovered;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;running&lt;/code&gt;: a worker owns the execution lease;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;completed&lt;/code&gt;: execution produced a recorded outcome;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;delivered&lt;/code&gt;: the outbound provider acknowledged the message;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unknown&lt;/code&gt;: the process died during a side-effect boundary and reconciliation is required;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;failed&lt;/code&gt;: the system has a terminal, evidenced failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A run can be &lt;code&gt;completed&lt;/code&gt; while delivery is &lt;code&gt;unknown&lt;/code&gt;. Treating those as the same state is how operators report success when the user saw nothing.&lt;/p&gt;

&lt;p&gt;Use stable IDs for both the request and each outbound effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request_id = uuid()
effect_id = hash(request_id + "completion-announcement")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact storage technology is less important than the invariant: a restart must not create a second logical request or a second outbound effect just because the first process disappeared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 1: ingress durability
&lt;/h2&gt;

&lt;p&gt;Send two messages with a small gap, then inspect the durable boundary rather than the chat UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;send(message_a)
send(message_b)

assert distinct(request_id_a, request_id_b)
assert queue.contains(request_id_a)
assert queue.contains(request_id_b)
assert queue.order_is_observable(request_id_a, request_id_b)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inject failures between receipt and queue commit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;kill the gateway after parsing but before the insert;&lt;/li&gt;
&lt;li&gt;delay the database response after the insert;&lt;/li&gt;
&lt;li&gt;retry the same delivery with the same provider event ID;&lt;/li&gt;
&lt;li&gt;deliver two messages concurrently.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For every case, the result should be one of two explicit outcomes: the request is durably accepted exactly once, or the caller receives a retryable failure. "No queue entry and no error" is not an acceptable state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 2: execution recovery
&lt;/h2&gt;

&lt;p&gt;Start a tool call that takes longer than the gateway process. Terminate the process while the call is pending, then restart it.&lt;/p&gt;

&lt;p&gt;The recovery contract must say which of these is true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the tool call can be safely resumed;&lt;/li&gt;
&lt;li&gt;the call is cancelled and the agent compensates;&lt;/li&gt;
&lt;li&gt;the outcome is &lt;code&gt;unknown&lt;/code&gt; and a reconciler checks the provider;&lt;/li&gt;
&lt;li&gt;the operation is deliberately non-replayable and needs human review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not infer completion from a worker log line. Persist a phase transition before dispatch and persist the provider result after the call. A crash between those writes is exactly the boundary your test must exercise.&lt;/p&gt;

&lt;p&gt;For non-idempotent tools, a stable effect key and provider-side lookup are safer than blind replay. If the provider has no lookup API, quarantine the operation as &lt;code&gt;unknown&lt;/code&gt; instead of guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 3: outbound delivery recovery
&lt;/h2&gt;

&lt;p&gt;Execution and delivery need separate ledgers. Simulate a crash after the announcement is accepted by the provider but before your worker records the acknowledgement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if delivery.status == "unknown":
    provider_result = lookup_by_idempotency_key(effect_id)
    if provider_result.sent:
        mark_delivered(effect_id, provider_result.message_id)
    else:
        retry_once_with_same_effect_id(effect_id)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then test the inverse boundary: the worker records success, but the network response is lost. The next process must reconcile before sending a duplicate.&lt;/p&gt;

&lt;p&gt;This is especially important for spawned jobs, cron tasks, and announce-style messages. A queue dashboard showing &lt;code&gt;pending&lt;/code&gt; tells you little unless you also know whether the consumer attempted delivery, whether the provider accepted it, and whether the result was reconciled after a restart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test 4: background-work truthfulness
&lt;/h2&gt;

&lt;p&gt;Run a scheduled job that completes its internal work but cannot publish its result. Verify that the UI and alerting surface say:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;execution: &lt;code&gt;completed&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;delivery: &lt;code&gt;failed&lt;/code&gt; or &lt;code&gt;unknown&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;recovery action: retry, reconcile, or human review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single green check should never hide a red delivery state.&lt;/p&gt;

&lt;p&gt;Track these counters separately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;accepted requests;&lt;/li&gt;
&lt;li&gt;queue insert failures;&lt;/li&gt;
&lt;li&gt;lease expirations;&lt;/li&gt;
&lt;li&gt;executions recovered after restart;&lt;/li&gt;
&lt;li&gt;unknown effects awaiting reconciliation;&lt;/li&gt;
&lt;li&gt;deliveries acknowledged by the provider;&lt;/li&gt;
&lt;li&gt;duplicate attempts suppressed by the effect key.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A compact failure matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Boundary&lt;/th&gt;
&lt;th&gt;Injected failure&lt;/th&gt;
&lt;th&gt;Required evidence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ingress to queue&lt;/td&gt;
&lt;td&gt;process kill&lt;/td&gt;
&lt;td&gt;durable request ID or retryable error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;queue to worker&lt;/td&gt;
&lt;td&gt;lease expiry&lt;/td&gt;
&lt;td&gt;one owner or explicit requeue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worker to tool&lt;/td&gt;
&lt;td&gt;process kill&lt;/td&gt;
&lt;td&gt;resume, compensate, or &lt;code&gt;unknown&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tool to ledger&lt;/td&gt;
&lt;td&gt;lost response&lt;/td&gt;
&lt;td&gt;provider lookup or quarantine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ledger to outbound provider&lt;/td&gt;
&lt;td&gt;network loss&lt;/td&gt;
&lt;td&gt;same effect key, no blind duplicate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;provider ack to UI&lt;/td&gt;
&lt;td&gt;callback loss&lt;/td&gt;
&lt;td&gt;delivery reconciliation and alert&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run this matrix on a clean environment and after every change to queueing, worker leases, provider adapters, or gateway restart behavior. If you run an always-on OpenClaw deployment, hosting is only one layer of the system and does not supply these state contracts. You still need the ledgers, idempotency keys, isolation, and recovery tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical definition of healthy
&lt;/h2&gt;

&lt;p&gt;A healthy agent gateway is not merely a live process. It can prove, for every request:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;where the request became durable;&lt;/li&gt;
&lt;li&gt;which worker owned execution;&lt;/li&gt;
&lt;li&gt;what happened at each tool boundary;&lt;/li&gt;
&lt;li&gt;whether outbound delivery was acknowledged;&lt;/li&gt;
&lt;li&gt;what recovery action is safe after a crash.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Start with one failure injection per boundary. Make the evidence queryable. Then make the health check fail when a contract is violated, not only when the process stops responding.&lt;/p&gt;

&lt;p&gt;That is the difference between monitoring uptime and testing whether the agent can be trusted unattended.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>agents</category>
      <category>openclaw</category>
      <category>testing</category>
    </item>
    <item>
      <title>Two AI Agents Read One Row. How to Stop the Lost Update</title>
      <dc:creator>Zira</dc:creator>
      <pubDate>Sat, 22 Aug 2026 21:36:55 +0000</pubDate>
      <link>https://dev.to/zira125/two-ai-agents-read-one-row-how-to-stop-the-lost-update-1025</link>
      <guid>https://dev.to/zira125/two-ai-agents-read-one-row-how-to-stop-the-lost-update-1025</guid>
      <description>&lt;p&gt;A version check is not automatically optimistic concurrency control.&lt;/p&gt;

&lt;p&gt;If an agent reads a row, reasons for a few seconds, and then writes a decision, another worker can change that row in the read-to-write gap. The final UPDATE may still return success while silently overwriting newer state.&lt;/p&gt;

&lt;p&gt;This is especially easy to miss in agent workflows because the reasoning step is slow, retries are common, and the write often looks like an ordinary CRUD operation.&lt;/p&gt;

&lt;p&gt;This post builds a small failure lab and turns the race into a testable contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug: the check happened too early
&lt;/h2&gt;

&lt;p&gt;A tempting implementation is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;grep -n "version" worker.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Read the row and its version.&lt;/li&gt;
&lt;li&gt;Check that the version is acceptable.&lt;/li&gt;
&lt;li&gt;Ask the model what to do.&lt;/li&gt;
&lt;li&gt;Update the row later.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The check in step 2 says nothing about whether the row is still current in step 4. Two workers can both read version 7, both produce valid decisions, and both write. The second write wins even though its input was stale.&lt;/p&gt;

&lt;p&gt;The failure is not necessarily a database error. It is a successful write based on an invalid snapshot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make freshness part of the write predicate
&lt;/h2&gt;

&lt;p&gt;Use a monotonic version, and require the version observed by the worker to match the version being replaced:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;action = db.execute(
    """
    UPDATE agent_tasks
       SET status = :new_status,
           decision = :decision,
           version = version + 1,
           updated_at = CURRENT_TIMESTAMP
     WHERE task_id = :task_id
       AND version = :expected_version
       AND status = 'ready'
    """,
    {
        "task_id": task_id,
        "expected_version": observed_version,
        "new_status": new_status,
        "decision": decision,
    },
)

if action.rowcount != 1:
    raise StalePlan("the row changed after the agent read it")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The important property is that the read and the model call do not need to be inside one long database transaction. The final compare-and-set write is the fence.&lt;/p&gt;

&lt;p&gt;A zero-row update is not a generic retry signal. It means the plan was produced from a snapshot that no longer owns the right to change the row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the plan tied to the snapshot
&lt;/h2&gt;

&lt;p&gt;Do not pass only a task ID into the write path. Persist the inputs that made the decision:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE agent_plans (
  plan_id TEXT PRIMARY KEY,
  task_id TEXT NOT NULL,
  observed_version INTEGER NOT NULL,
  input_hash TEXT NOT NULL,
  decision_json TEXT NOT NULL,
  state TEXT NOT NULL CHECK (state IN ('proposed', 'applied', 'stale', 'unknown')),
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The input hash should cover the normalized state the model actually saw, not just the database primary key. If the prompt included policy, tool availability, or a selected artifact, include their versions too.&lt;/p&gt;

&lt;p&gt;At apply time, verify both the row version and the plan state. A plan that was already applied must not be applied a second time, even if a client retries after a timeout.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal failure-injection test
&lt;/h2&gt;

&lt;p&gt;You can reproduce the race without an LLM. Replace the reasoning step with a barrier so both workers pause after reading the same version:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async def worker(barrier, task_id):
    row = await read_task(task_id)
    await barrier.wait()  # both workers now hold the same snapshot
    decision = {"owner": current_worker_id()}
    return await apply_plan(
        task_id=task_id,
        observed_version=row.version,
        decision=decision,
    )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The expected result is exactly one successful apply and one stale-plan result. If both calls report success, the write fence is missing or the test is not exercising the real update path.&lt;/p&gt;

&lt;p&gt;Run the lab with these cases:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Case&lt;/th&gt;
&lt;th&gt;Expected result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Two workers read version 7&lt;/td&gt;
&lt;td&gt;One applies; one is stale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker retries the same plan&lt;/td&gt;
&lt;td&gt;No second business effect&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Row changes while the model is thinking&lt;/td&gt;
&lt;td&gt;Apply is rejected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Policy version changes&lt;/td&gt;
&lt;td&gt;Plan is rejected or re-planned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database response is lost after commit&lt;/td&gt;
&lt;td&gt;Outcome becomes UNKNOWN and is reconciled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker restarts after apply&lt;/td&gt;
&lt;td&gt;Recovery does not apply the plan again&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Treat timeout after write as UNKNOWN
&lt;/h2&gt;

&lt;p&gt;A client timeout does not prove that the UPDATE failed. The database may have committed before the connection broke.&lt;/p&gt;

&lt;p&gt;Give every apply attempt a stable effect key, such as task_id plus plan_id, and record it in a durable effect ledger. On a timeout:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Look up the effect key.&lt;/li&gt;
&lt;li&gt;Compare the stored plan and row version.&lt;/li&gt;
&lt;li&gt;Mark the outcome applied, stale, or UNKNOWN.&lt;/li&gt;
&lt;li&gt;Reconcile UNKNOWN before allowing another attempt.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Never solve an ambiguous write by blindly rerunning the model and applying its new answer. That can turn one uncertain side effect into two different decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to monitor
&lt;/h2&gt;

&lt;p&gt;A useful dashboard separates these signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stale-plan rejects, by tool and workflow;&lt;/li&gt;
&lt;li&gt;plans that waited longer than their snapshot budget;&lt;/li&gt;
&lt;li&gt;duplicate apply attempts by effect key;&lt;/li&gt;
&lt;li&gt;UNKNOWN outcomes and reconciliation age;&lt;/li&gt;
&lt;li&gt;policy or tool-contract changes between read and apply;&lt;/li&gt;
&lt;li&gt;successful writes that had no matching plan record.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last metric should be zero. If it is not, some path can mutate agent state without the same evidence and fencing rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;p&gt;Before trusting an agent that writes shared state, verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model decision carries the exact version and policy inputs it observed.&lt;/li&gt;
&lt;li&gt;The final write uses an atomic compare-and-set predicate.&lt;/li&gt;
&lt;li&gt;A zero-row update becomes a typed stale-plan result, not a generic retry.&lt;/li&gt;
&lt;li&gt;Every business effect has a stable idempotency key.&lt;/li&gt;
&lt;li&gt;Lost responses are reconciled instead of replayed blindly.&lt;/li&gt;
&lt;li&gt;A barrier test proves two concurrent readers cannot both apply version 7.&lt;/li&gt;
&lt;li&gt;Restarts and partial failures are included in the test matrix.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A version column is useful, but the real boundary is the version check at the write. Without that fence, an agent can be perfectly correct about an old snapshot and still corrupt the current one.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>agents</category>
      <category>postgres</category>
      <category>testing</category>
    </item>
    <item>
      <title>Your AI Agent Returned HTTP 200. Why Did the Workflow Still Fail?</title>
      <dc:creator>Zira</dc:creator>
      <pubDate>Fri, 21 Aug 2026 13:34:21 +0000</pubDate>
      <link>https://dev.to/zira125/your-ai-agent-returned-http-200-why-did-the-workflow-still-fail-452o</link>
      <guid>https://dev.to/zira125/your-ai-agent-returned-http-200-why-did-the-workflow-still-fail-452o</guid>
      <description>&lt;p&gt;A successful HTTP response is not a successful agent run.&lt;/p&gt;

&lt;p&gt;A recent practitioner report from a 58-day deployment of 78 agents recorded 6,768 failed outputs. The failures were not transport errors: every one returned HTTP 200, had plausible length, and looked fluent. The most expensive failures were boring shape mismatches: missing required fields, wrong language, forbidden phrases, or an answer for a different stage.&lt;/p&gt;

&lt;p&gt;That is a useful warning for anyone building coding agents, review agents, or unattended automation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Treat the model response as untrusted data. Validate the contract at the boundary before another stage can consume it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This post turns that observation into a small, reproducible failure lab.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure you should be able to reproduce
&lt;/h2&gt;

&lt;p&gt;Imagine a review stage whose downstream parser expects a verdict line:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;action: approve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A model can return a thoughtful review with the verdict buried in prose. A human approves it. A parser does not.&lt;/p&gt;

&lt;p&gt;The transport layer is green. The model call is green. The workflow is broken.&lt;/p&gt;

&lt;p&gt;The same class of failure appears when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a JSON field is present but has the wrong type&lt;/li&gt;
&lt;li&gt;a response is in the wrong language&lt;/li&gt;
&lt;li&gt;a tool returns an error string inside a successful content envelope&lt;/li&gt;
&lt;li&gt;a stage emits output, but the next stage never reads that artifact&lt;/li&gt;
&lt;li&gt;a reviewer from the same model family approves a shared blind spot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not reasons to add a larger model first. They are reasons to make the boundary observable and enforceable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a contract gate
&lt;/h2&gt;

&lt;p&gt;Start with deterministic checks that do not ask an LLM to judge another LLM.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def validate_review(text: str) -&amp;gt; list[str]:
    errors = []

    if len(text.strip()) &amp;lt; 150:
        errors.append('too_short')

    if not any(line.startswith('判定:') or line.startswith('判定：')
               for line in text.splitlines()):
        errors.append('missing_required_verdict')

    forbidden = ['お客様の声', '顧客の声']
    if any(term in text for term in forbidden):
        errors.append('forbidden_phrase')

    if not any('。' in line for line in text.splitlines()):
        errors.append('expected_language_missing')

    return errors

errors = validate_review(model_output)
if errors:
    record_rejected_output(errors, model_output)
    stop_downstream_dispatch()
else:
    publish_to_next_stage(model_output)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The important part is not the exact Japanese check. Replace it with the contract your system actually needs: required headings, schema types, repository paths, test names, citation fields, or a bounded action list.&lt;/p&gt;

&lt;p&gt;A gate should return structured evidence, not only true or false:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;action: reject
reasons:
  - missing_required_verdict
  - forbidden_phrase
contract_version: review-v3
artifact_id: art_01J...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;That makes a failure repairable instead of turning it into a green dashboard with a missing deliverable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Record why, not just that
&lt;/h2&gt;

&lt;p&gt;A common anti-pattern is storing only a boolean such as contract_satisfied = false. That destroys the information needed to debug drift.&lt;/p&gt;

&lt;p&gt;Store at least:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;artifact_id&lt;/td&gt;
&lt;td&gt;Connects the output to its producer and consumer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;contract_version&lt;/td&gt;
&lt;td&gt;Shows which rules were active&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;observed_checks&lt;/td&gt;
&lt;td&gt;Proves what was actually tested&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;failure_reasons&lt;/td&gt;
&lt;td&gt;Separates shape, language, policy, and transport failures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;raw_output_hash&lt;/td&gt;
&lt;td&gt;Allows correlation without exposing sensitive content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;downstream_read_at&lt;/td&gt;
&lt;td&gt;Detects outputs that nobody consumed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reviewer_family&lt;/td&gt;
&lt;td&gt;Exposes correlated writer/reviewer blind spots&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not silently discard rejected output. Apply retention and redaction rules, but preserve enough evidence to answer: what was produced, which contract rejected it, and did any later stage read it?&lt;/p&gt;

&lt;p&gt;This is the same evidence discipline I use in &lt;a href="https://dev.to/zira125/your-ai-agent-logs-are-not-an-audit-trail-until-you-test-the-evidence-19ld"&gt;audit-ready agent logs&lt;/a&gt;: an event saying “run completed” is weaker than a record of the checks and artifacts that made completion meaningful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add an artifact-lineage check
&lt;/h2&gt;

&lt;p&gt;One surprising failure mode is a healthy upstream stage whose output is never used. Test this explicitly.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give every produced artifact a stable ID.&lt;/li&gt;
&lt;li&gt;Require the consumer to record the input artifact ID.&lt;/li&gt;
&lt;li&gt;Reject a stage that claims success without a consumed input ID.&lt;/li&gt;
&lt;li&gt;Compare produced and consumed counts over a time window.&lt;/li&gt;
&lt;li&gt;Alert on a growing gap, even if every process heartbeat is green.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This catches wiring bugs that output-quality checks cannot see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the failure lab
&lt;/h2&gt;

&lt;p&gt;Before trusting a new agent workflow, inject each case and verify the expected evidence:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Injection&lt;/th&gt;
&lt;th&gt;Expected result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Remove the required verdict line&lt;/td&gt;
&lt;td&gt;Reject before downstream dispatch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Return valid-length text in the wrong language&lt;/td&gt;
&lt;td&gt;Reject with language evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Put an error string in a successful tool envelope&lt;/td&gt;
&lt;td&gt;Mark the tool call failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drop the artifact ID between stages&lt;/td&gt;
&lt;td&gt;Block consumption and alert on lineage gap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change the contract version mid-run&lt;/td&gt;
&lt;td&gt;Revalidate or move the run to UNKNOWN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Make writer and reviewer share a known blind spot&lt;/td&gt;
&lt;td&gt;Require an independent check or human review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crash after provider acceptance but before ledger write&lt;/td&gt;
&lt;td&gt;Reconcile before retrying&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last case matters for side effects. A contract gate protects output shape; it does not prove that an external action did or did not happen. Keep execution evidence and outbound-delivery evidence separate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where hosting fits
&lt;/h2&gt;

&lt;p&gt;An always-on runtime can keep schedulers, workers, and evidence writers available, but hosting does not define your output contract or make a green HTTP response meaningful. If you need managed infrastructure for an unattended OpenClaw workload, &lt;a href="https://ampere.sh/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=output-contract-failure-lab" rel="noopener noreferrer"&gt;managed OpenClaw hosting on Ampere&lt;/a&gt; is one option to evaluate. You still own validation, credential scope, prompt-injection defenses, and reconciliation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical checklist
&lt;/h2&gt;

&lt;p&gt;Before shipping an agent stage, verify that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;transport success is distinct from contract success&lt;/li&gt;
&lt;li&gt;required fields and language are machine-checked&lt;/li&gt;
&lt;li&gt;rejection reasons are durable and queryable&lt;/li&gt;
&lt;li&gt;artifact lineage connects producers to consumers&lt;/li&gt;
&lt;li&gt;the reviewer cannot share every blind spot with the writer&lt;/li&gt;
&lt;li&gt;output quality and side-effect completion are separate states&lt;/li&gt;
&lt;li&gt;failure injection covers both malformed output and crash windows&lt;/li&gt;
&lt;li&gt;dashboards count produced and consumed artifacts, not only invocations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The question is not “did the model answer?” It is “did a versioned, observable contract accept an artifact that the next stage actually consumed?”&lt;/p&gt;

&lt;p&gt;That is the difference between an agent that is alive and a workflow that is working.&lt;/p&gt;

&lt;p&gt;If you build AI agents or developer tooling, follow me for practical failure labs and reproducible control-boundary tests rather than capability demos.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>devops</category>
      <category>agents</category>
      <category>ai</category>
    </item>
    <item>
      <title>Your AI Agent Cache Needs a Trust Boundary, Not Just a TTL</title>
      <dc:creator>Zira</dc:creator>
      <pubDate>Thu, 20 Aug 2026 07:46:45 +0000</pubDate>
      <link>https://dev.to/zira125/your-ai-agent-cache-needs-a-trust-boundary-not-just-a-ttl-46p0</link>
      <guid>https://dev.to/zira125/your-ai-agent-cache-needs-a-trust-boundary-not-just-a-ttl-46p0</guid>
      <description>&lt;p&gt;A short TTL does not make an agent's cached tool result safe.&lt;/p&gt;

&lt;p&gt;A result can still be wrong for the current user, tenant, credential epoch, policy version, or side-effect state. Worse, a cache can turn an old success into a fresh-looking authorization decision.&lt;/p&gt;

&lt;p&gt;For agents, cache correctness is not only about freshness. It is about whether the result is still trustworthy for this run.&lt;/p&gt;

&lt;p&gt;This article builds a small cache contract, shows where to enforce it, and ends with failure-injection tests you can run before putting cached tool results in a long-lived runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure mode: fresh enough, wrong authority
&lt;/h2&gt;

&lt;p&gt;Imagine an agent that calls &lt;code&gt;get_invoice&lt;/code&gt; and caches the result for five minutes. During those five minutes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the user loses access to the account;&lt;/li&gt;
&lt;li&gt;the tenant policy changes from allow to deny;&lt;/li&gt;
&lt;li&gt;the credential is revoked or rotated;&lt;/li&gt;
&lt;li&gt;the resource moves to another tenant;&lt;/li&gt;
&lt;li&gt;the tool contract changes its meaning;&lt;/li&gt;
&lt;li&gt;a previous request completed with an UNKNOWN external outcome.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A normal TTL check answers only “Is this entry old?” It does not answer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Was this entry produced for the same principal and tenant?&lt;/li&gt;
&lt;li&gt;Was it produced under a still-valid policy and credential epoch?&lt;/li&gt;
&lt;li&gt;Does the current tool contract interpret the result the same way?&lt;/li&gt;
&lt;li&gt;Is the result safe to reuse for this operation, or was it only an observation?&lt;/li&gt;
&lt;li&gt;Can the caller prove where the value came from?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Treating those as separate checks prevents a cache hit from silently becoming an authorization bypass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the cache entry as evidence
&lt;/h2&gt;

&lt;p&gt;Do not store only &lt;code&gt;key -&amp;gt; value&lt;/code&gt;. Store the context that makes the value valid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cache_key = hash(
  tool_name,
  normalized_arguments,
  principal_id,
  tenant_id,
  resource_scope,
  policy_version,
  credential_epoch,
  tool_contract_version
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful entry also contains &lt;code&gt;produced_at&lt;/code&gt;, &lt;code&gt;expires_at&lt;/code&gt;, the producing run ID, a provider request ID, a sensitivity classification, a schema hash, and a revocation or invalidation sequence.&lt;/p&gt;

&lt;p&gt;The key prevents accidental cross-context reuse. The metadata lets the read path reject entries that are technically fresh but no longer valid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate observation from authority
&lt;/h2&gt;

&lt;p&gt;The safest default is to cache observations, not permissions.&lt;/p&gt;

&lt;p&gt;A cached “invoice total is 42” may be useful for display, but it should not be treated as proof that the current run may refund that invoice. The refund path should perform a current authorization check and, where necessary, a fresh resource read.&lt;/p&gt;

&lt;p&gt;A practical classification is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Reuse rule&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OBSERVATION&lt;/td&gt;
&lt;td&gt;list of recent logs&lt;/td&gt;
&lt;td&gt;Reuse only within scope and freshness bounds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ADVISORY&lt;/td&gt;
&lt;td&gt;model-generated summary&lt;/td&gt;
&lt;td&gt;Never use as authorization evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DECISION_INPUT&lt;/td&gt;
&lt;td&gt;account state used for a plan&lt;/td&gt;
&lt;td&gt;Revalidate policy and resource version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EFFECT_CONFIRMATION&lt;/td&gt;
&lt;td&gt;provider request ID&lt;/td&gt;
&lt;td&gt;Reconcile by stable effect key, not TTL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This also protects against using a cache to “remember” that an external write succeeded. A timeout after dispatch is an UNKNOWN outcome. It needs provider reconciliation, not a cache hit that happens to contain a similar response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforce the contract at read time
&lt;/h2&gt;

&lt;p&gt;The read path should reconstruct the current context and reject mismatches before returning a hit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;usable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="n"&gt;same_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;principal_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;principal_id&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_scope&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resource_scope&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;same_versions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;policy_version&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;policy_version&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;credential_epoch&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;credential_epoch&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_contract_version&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_contract_version&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;same_context&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;same_versions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;classification&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DECISION_INPUT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;effect_confirmation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# reconcile by effect key instead
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The invariant is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A cache hit is usable only when its validity context matches the current run and its classification permits the requested use.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Do not rely on the model to decide whether a cached value is safe. The model can request a cache read, but a deterministic layer should decide whether the read is allowed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make invalidation explicit
&lt;/h2&gt;

&lt;p&gt;TTL is a fallback. Important changes should invalidate immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;policy version increment;&lt;/li&gt;
&lt;li&gt;credential epoch increment;&lt;/li&gt;
&lt;li&gt;tenant membership or resource ownership change;&lt;/li&gt;
&lt;li&gt;tool schema or contract change;&lt;/li&gt;
&lt;li&gt;manual incident response;&lt;/li&gt;
&lt;li&gt;deletion or retention event;&lt;/li&gt;
&lt;li&gt;provider reconciliation that contradicts the cached result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An append-only invalidation record makes the behavior inspectable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;cache_invalidations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;scope&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;version&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Writers attach the current invalidation version to each entry. Readers reject entries written before the active version for the relevant scope.&lt;/p&gt;

&lt;p&gt;That is safer than trying to find and delete every matching key during an incident. Deletion can be incomplete; a version boundary is easy to check and audit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prevent stampedes without widening authority
&lt;/h2&gt;

&lt;p&gt;When many runs miss simultaneously, single-flight loading can help, but the lock must not blur security boundaries.&lt;/p&gt;

&lt;p&gt;Use a lock key with the same principal, tenant, resource, and version dimensions as the cache key. Never let a privileged request populate a shared entry that an unprivileged request can read.&lt;/p&gt;

&lt;p&gt;Bound the wait:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;attempt a scoped cache read;&lt;/li&gt;
&lt;li&gt;if missing, acquire a scoped single-flight lease;&lt;/li&gt;
&lt;li&gt;recheck the cache after acquiring the lease;&lt;/li&gt;
&lt;li&gt;load from the provider with current credentials;&lt;/li&gt;
&lt;li&gt;validate the response schema and resource scope;&lt;/li&gt;
&lt;li&gt;write the entry with its context and invalidation version;&lt;/li&gt;
&lt;li&gt;release the lease.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the provider call times out after dispatch, do not populate a success entry. Record UNKNOWN and reconcile with the provider using a stable request key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure-injection checklist
&lt;/h2&gt;

&lt;p&gt;Add these tests to CI or a staging drill:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;change the tenant between write and read;&lt;/li&gt;
&lt;li&gt;revoke the credential while the entry is within TTL;&lt;/li&gt;
&lt;li&gt;increment policy version while the entry is fresh;&lt;/li&gt;
&lt;li&gt;change the tool contract or result schema;&lt;/li&gt;
&lt;li&gt;replay identical arguments under a different principal;&lt;/li&gt;
&lt;li&gt;return an observation on an authorization path;&lt;/li&gt;
&lt;li&gt;crash after provider dispatch but before cache write;&lt;/li&gt;
&lt;li&gt;make the provider return an UNKNOWN timeout;&lt;/li&gt;
&lt;li&gt;let two tenants contend for normalized arguments;&lt;/li&gt;
&lt;li&gt;restore stale cache entries with current policy versions;&lt;/li&gt;
&lt;li&gt;fill the cache with an oversized or sensitive result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For every test, record the expected decision: HIT, MISS, REVALIDATE, DENY, or RECONCILE. A green suite should show not only that the request failed safely, but why.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to monitor
&lt;/h2&gt;

&lt;p&gt;Track cache behavior as a control-plane signal, not just a latency metric:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hit and miss rate by classification;&lt;/li&gt;
&lt;li&gt;rejected hits by reason;&lt;/li&gt;
&lt;li&gt;cross-context mismatch attempts;&lt;/li&gt;
&lt;li&gt;policy and credential invalidations;&lt;/li&gt;
&lt;li&gt;stale-entry reads after restore;&lt;/li&gt;
&lt;li&gt;UNKNOWN outcomes awaiting reconciliation;&lt;/li&gt;
&lt;li&gt;cache fill failures and provider request IDs;&lt;/li&gt;
&lt;li&gt;sensitive-data eviction and retention events.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A rising hit rate can be a bug if rejection and revalidation data disappear. Keep enough evidence to answer which context produced a value and why the current run was allowed to use it.&lt;/p&gt;

&lt;p&gt;If you need a managed place to run an always-on OpenClaw or browser-automation workload, &lt;a href="https://ampere.sh/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=agent-cache-boundary" rel="noopener noreferrer"&gt;managed OpenClaw hosting on Ampere&lt;/a&gt; is one option to evaluate. Hosting does not define cache authority, invalidation, credential scope, or reconciliation semantics; those contracts still belong in the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final rule
&lt;/h2&gt;

&lt;p&gt;A TTL answers “how old is this value?” It does not answer “who may trust it now?”&lt;/p&gt;

&lt;p&gt;For agent systems, bind cache entries to identity, tenant, resource scope, policy version, credential epoch, and tool contract. Classify what the value is allowed to prove. Invalidate by version when authority changes. Reconcile external effects instead of treating cached responses as confirmation.&lt;/p&gt;

&lt;p&gt;That turns caching from a speed optimization into an explicit, testable part of the agent's control plane.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>agents</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>Your AI Agent Upgrade Needs a Compatibility Gate, Not Just a Rollback</title>
      <dc:creator>Zira</dc:creator>
      <pubDate>Thu, 20 Aug 2026 03:44:13 +0000</pubDate>
      <link>https://dev.to/zira125/your-ai-agent-upgrade-needs-a-compatibility-gate-not-just-a-rollback-4nfa</link>
      <guid>https://dev.to/zira125/your-ai-agent-upgrade-needs-a-compatibility-gate-not-just-a-rollback-4nfa</guid>
      <description>&lt;p&gt;A rollback answers one question: How do I put the old binary back?&lt;/p&gt;

&lt;p&gt;It does not answer the harder question: What happens to work, state, credentials, and queued effects that crossed the version boundary while the upgrade was running?&lt;/p&gt;

&lt;p&gt;For an AI agent, that boundary is everywhere. A new worker may read an old state row, a queued tool call may use a changed schema, or a restarted process may replay an effect created by the previous release. A clean process rollback can still leave an incompatible state store and duplicate side effects.&lt;/p&gt;

&lt;p&gt;This article presents a small compatibility gate you can run before and during an agent-runtime upgrade.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contract to gate
&lt;/h2&gt;

&lt;p&gt;Give every persisted object and outbound effect an explicit producer contract:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;default_version = {
  state_schema: 7,
  tool_contracts: { browser_navigate: 3, repo_apply_patch: 2 },
  effect_protocol: 2,
  credential_epoch: 19
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The exact format is not important. The important part is that the worker cannot silently assume that the current version is compatible. Persist the versions beside the run, task, queue item, and effect record.&lt;/p&gt;

&lt;p&gt;A worker should accept work only when it can prove all of these conditions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It can read the state schema and preserve fields it does not own.&lt;/li&gt;
&lt;li&gt;Its tool-input and tool-output contracts match the queued request.&lt;/li&gt;
&lt;li&gt;Its effect protocol understands the existing idempotency and reconciliation states.&lt;/li&gt;
&lt;li&gt;Its credential epoch is still authorized for this run.&lt;/li&gt;
&lt;li&gt;It can write evidence in a format the recovery tooling understands.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any check is unknown, pause the item. Do not guess that an old payload is harmless.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use expand, gate, migrate, contract
&lt;/h2&gt;

&lt;p&gt;A safe rollout is usually four phases:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Expand
&lt;/h3&gt;

&lt;p&gt;Deploy readers that understand both the old and new representation. Add new columns or fields without deleting old ones. New writers should continue producing the old representation until the compatibility gate is live.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Gate
&lt;/h3&gt;

&lt;p&gt;Put the new worker in a canary mode. It may claim only runs whose contract passes the compatibility test. Keep a visible counter for rejected, unknown, and accepted items.&lt;/p&gt;

&lt;p&gt;A useful gate result is more informative than a boolean:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def check_compatibility(item, worker):
    reasons = []
    if item.state_schema not in worker.readable_state_schemas:
        reasons.append("state-schema")
    if item.effect_protocol not in worker.effect_protocols:
        reasons.append("effect-protocol")
    if item.credential_epoch &amp;gt; worker.max_authorized_epoch:
        reasons.append("credential-epoch")
    return {"decision": "reject" if reasons else "accept", "reasons": reasons}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In production code, compare capabilities rather than requiring every version number to be identical. A worker that can safely read schema 6 and 7 should say so explicitly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Migrate
&lt;/h3&gt;

&lt;p&gt;Migrate durable state in resumable, idempotent batches. Record the source version, destination version, migration attempt, and checksum. If a migration stops halfway through, rerunning it should either complete the same transformation or report a deterministic conflict.&lt;/p&gt;

&lt;p&gt;Never treat a successful database transaction as proof that an external effect completed. Keep execution state and delivery state separate. An upgrade can commit a dispatch-requested row immediately before the worker dies.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Contract
&lt;/h3&gt;

&lt;p&gt;Only after the queue is drained or gated, the state is migrated, and reconciliation is complete should you remove the old reader and writer path. Keep the old path long enough to recover records created before the cutover.&lt;/p&gt;

&lt;h2&gt;
  
  
  What rollback must preserve
&lt;/h2&gt;

&lt;p&gt;Before declaring a rollback successful, verify these invariants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every claimed run has one owner or an explicit expired lease.&lt;/li&gt;
&lt;li&gt;Every tool call has a stable idempotency key.&lt;/li&gt;
&lt;li&gt;Every outbound effect is CONFIRMED, NOT_SENT, or UNKNOWN, never silently absent.&lt;/li&gt;
&lt;li&gt;Every UNKNOWN effect has a provider lookup or a human/operator decision path.&lt;/li&gt;
&lt;li&gt;Credential revocation and policy changes apply to both old and new workers.&lt;/li&gt;
&lt;li&gt;The evidence writer can still render the run history after the version change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why a rollback should restore a compatible runtime, not merely an older container image.&lt;/p&gt;

&lt;h2&gt;
  
  
  A failure-injection drill
&lt;/h2&gt;

&lt;p&gt;Run this in a staging environment with a disposable state store:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Queue a task using the old tool contract.&lt;/li&gt;
&lt;li&gt;Claim it with the new worker, then kill the worker after the claim transaction.&lt;/li&gt;
&lt;li&gt;Change the credential epoch before the worker restarts.&lt;/li&gt;
&lt;li&gt;Deploy the old worker and attempt recovery.&lt;/li&gt;
&lt;li&gt;Inject a timeout after an outbound provider accepts the request but before your ledger records the response.&lt;/li&gt;
&lt;li&gt;Run reconciliation, then inspect whether the task is resumed, paused, or marked unknown.&lt;/li&gt;
&lt;li&gt;Repeat with a partially migrated state row and a duplicate delivery attempt.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The expected result is not that everything finishes. The expected result is that every ambiguous transition becomes visible and no stale worker can perform an effect after losing authority.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosting is part of the recovery surface
&lt;/h2&gt;

&lt;p&gt;If the agent must stay available during a migration or recovery window, a managed runtime can reduce the operational work of keeping the process online. For teams evaluating that option, &lt;a href="https://ampere.sh/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=agent-compatibility-gate" rel="noopener noreferrer"&gt;managed OpenClaw hosting on Ampere&lt;/a&gt; is one hosting path to compare.&lt;/p&gt;

&lt;p&gt;That does not provide the compatibility contract for you. You still need durable state, scoped credentials, migration tests, reconciliation, and a rollback plan that accounts for work already in flight.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist
&lt;/h2&gt;

&lt;p&gt;Before upgrading an agent runtime, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which state schemas can the new worker read and write?&lt;/li&gt;
&lt;li&gt;Which queued tool contracts can it execute safely?&lt;/li&gt;
&lt;li&gt;What is the effect protocol for requests that are UNKNOWN at the cutover?&lt;/li&gt;
&lt;li&gt;How are credential and policy versions rechecked after restart?&lt;/li&gt;
&lt;li&gt;Can the migration resume without duplicating a transformation?&lt;/li&gt;
&lt;li&gt;Can the old runtime recover records created by the new runtime?&lt;/li&gt;
&lt;li&gt;Which evidence proves the cutover was safe?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you cannot answer those questions, you have a deployment plan and a rollback button, but not a compatibility gate.&lt;/p&gt;

&lt;p&gt;The right upgrade test is not whether the new process started. It is whether old and new workers can share the boundary without losing authority, state, or effect evidence.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>agents</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Your AI Agent's Health Check Lies Unless It Tests the Tool Path</title>
      <dc:creator>Zira</dc:creator>
      <pubDate>Wed, 19 Aug 2026 23:42:59 +0000</pubDate>
      <link>https://dev.to/zira125/your-ai-agents-health-check-lies-unless-it-tests-the-tool-path-48ma</link>
      <guid>https://dev.to/zira125/your-ai-agents-health-check-lies-unless-it-tests-the-tool-path-48ma</guid>
      <description>&lt;p&gt;A process can answer &lt;strong&gt;200 OK&lt;/strong&gt; while every useful action is failing.&lt;/p&gt;

&lt;p&gt;That is the dangerous kind of healthy state for an AI agent. The supervisor sees a live process, the container stays running, and the dashboard is green. Meanwhile the agent cannot reach its model gateway, its tool credentials are expired, its browser profile is locked, or its queue consumer has lost its lease.&lt;/p&gt;

&lt;p&gt;The fix is not to make one giant health endpoint that performs a real side effect. The fix is to model health as several explicit signals and test the path that matters without crossing the mutation boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate liveness, readiness, and capability
&lt;/h2&gt;

&lt;p&gt;Use at least three states:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Safe check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Liveness&lt;/td&gt;
&lt;td&gt;Is the process event loop responding?&lt;/td&gt;
&lt;td&gt;Local response with a deadline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Readiness&lt;/td&gt;
&lt;td&gt;Can this worker accept a new run?&lt;/td&gt;
&lt;td&gt;Queue lease, config, and dependency checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Capability&lt;/td&gt;
&lt;td&gt;Can this run perform its required tool calls?&lt;/td&gt;
&lt;td&gt;Non-mutating probes for the selected capability set&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Liveness should be cheap. A liveness failure usually means the process needs a restart.&lt;/p&gt;

&lt;p&gt;Readiness is an admission decision. A worker with a stale configuration version or no durable-state connection should stop receiving new work, even if its process is alive.&lt;/p&gt;

&lt;p&gt;Capability is run-specific. A text-only task may need a model gateway and a database, while a browser task also needs a valid browser profile, network policy, and download storage. Do not report one global green light when only half the capabilities are usable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the checks typed, not boolean
&lt;/h2&gt;

&lt;p&gt;A boolean loses the information needed for recovery. Return a typed result with an owner and a next action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"browser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DEGRADED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"checked_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-20T01:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"config_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cfg-184"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"credential_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cred-77"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"profile_lock"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"held-by-worker-12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"read_only_probe"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"passed"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"admission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reject_browser_runs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"recovery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"recycle_profile_after_lease_expiry"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful states are usually &lt;strong&gt;PASS&lt;/strong&gt;, &lt;strong&gt;DEGRADED&lt;/strong&gt;, &lt;strong&gt;FAIL&lt;/strong&gt;, and &lt;strong&gt;UNKNOWN&lt;/strong&gt;. &lt;strong&gt;UNKNOWN&lt;/strong&gt; matters when a probe timed out or a dependency returned an ambiguous result. It should not silently become &lt;strong&gt;PASS&lt;/strong&gt;, and it should not always trigger an aggressive restart.&lt;/p&gt;

&lt;p&gt;A typed result also makes alerts actionable. “Agent unhealthy” is not an operator instruction. “Reject browser runs, keep text runs admitted, recycle the profile after the lease expires” is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Probe dependencies without performing user mutations
&lt;/h2&gt;

&lt;p&gt;A tool-path probe should be representative but reversible. Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model gateway: authenticate, request the smallest allowed metadata or token-count operation, then record latency and policy version.&lt;/li&gt;
&lt;li&gt;Database: open a connection, verify schema version, and execute a read-only query with a deadline.&lt;/li&gt;
&lt;li&gt;Object storage: verify access to the expected bucket and prefix, without uploading a test object unless the system has a cleanup contract.&lt;/li&gt;
&lt;li&gt;Browser: launch a disposable profile, navigate only to an allowlisted diagnostic page, and verify the expected browser/CDP handshake.&lt;/li&gt;
&lt;li&gt;MCP: list the server’s declared tools and compare them with the policy snapshot, without invoking a mutating tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Never use “send an email to test email.” A health check that creates an external side effect is an incident generator. If a provider cannot offer a safe probe, classify that capability as &lt;strong&gt;UNVERIFIED&lt;/strong&gt; and rely on delivery receipts or a separate canary account with an explicit cleanup path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tie checks to the run's capability set
&lt;/h2&gt;

&lt;p&gt;Before admission, compute the required capability set from the task, not from the worker's startup profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;admit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;required_capabilities&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;capability&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;capability&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PASS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEGRADED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
            &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;capability&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REJECT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ADMIT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;health_snapshot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;health&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snapshot_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store the health snapshot ID with the run admission record. Otherwise an incident review cannot answer a basic question: did the worker admit the task before or after the dependency failed?&lt;/p&gt;

&lt;p&gt;The snapshot is not permanent authorization. Recheck policy, credential version, and lease ownership at dispatch time. Health proves a narrow observation at a point in time; it does not grant permission to perform a later action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the failure domains separately
&lt;/h2&gt;

&lt;p&gt;A useful test matrix injects one fault at a time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep the process responsive but block the model gateway. Liveness should pass; model capability should fail; new model runs should be rejected.&lt;/li&gt;
&lt;li&gt;Expire the browser credential while the process remains alive. Browser capability should become degraded or failed; text-only work should remain unaffected.&lt;/li&gt;
&lt;li&gt;Break the queue lease renewal. Readiness should fail after the lease deadline, and another worker should be able to claim new work.&lt;/li&gt;
&lt;li&gt;Return a timeout from a tool dispatch. The result should be &lt;strong&gt;UNKNOWN&lt;/strong&gt;, not an automatic retry that might duplicate a mutation.&lt;/li&gt;
&lt;li&gt;Roll the configuration version while a run is waiting. The worker should recheck at dispatch and either refresh safely or reject the run.&lt;/li&gt;
&lt;li&gt;Fill the diagnostic storage path. The probe should identify the capacity failure without turning every unrelated capability red.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For each test, record the expected admission decision, the alert, the recovery owner, and whether already-running work is allowed to finish. That last field prevents a readiness failure from becoming an unplanned cancellation storm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make recovery narrower than restart
&lt;/h2&gt;

&lt;p&gt;A failed capability does not always justify restarting the whole agent. Prefer the smallest recovery domain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refresh a short-lived credential when only its version is invalid.&lt;/li&gt;
&lt;li&gt;Recycle a disposable browser profile when the profile lock is stale.&lt;/li&gt;
&lt;li&gt;Pause admission when the queue lease is lost.&lt;/li&gt;
&lt;li&gt;Route around one unavailable model provider when policy permits.&lt;/li&gt;
&lt;li&gt;Restart the process only when liveness or local invariants fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For always-on OpenClaw or browser-agent deployments, the hosting layer is part of this recovery contract. A managed runtime such as &lt;a href="https://ampere.sh/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=agent-health-check" rel="noopener noreferrer"&gt;always-on OpenClaw hosting on Ampere&lt;/a&gt; can be a hosting option to evaluate when you need a persistent process and repeatable restart boundary, but it does not remove the need to define state persistence, credential scope, health probes, and recovery policy yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to put on the dashboard
&lt;/h2&gt;

&lt;p&gt;Show separate panels for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;process liveness and restart count;&lt;/li&gt;
&lt;li&gt;readiness and admission rejections;&lt;/li&gt;
&lt;li&gt;capability state by dependency and task type;&lt;/li&gt;
&lt;li&gt;probe latency and timeout rate;&lt;/li&gt;
&lt;li&gt;configuration and credential versions;&lt;/li&gt;
&lt;li&gt;unknown outcomes awaiting reconciliation;&lt;/li&gt;
&lt;li&gt;recovery actions and their completion status.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not a prettier green dashboard. The goal is to make “alive but unable to do useful work” impossible to confuse with “ready to accept this run.”&lt;/p&gt;

&lt;p&gt;If you build agents, add one tool-path probe this week. Then inject one dependency failure while the process stays alive. If the system cannot reject only the affected work, preserve the evidence, and recover the smallest possible domain, the health check is measuring uptime, not health.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>devops</category>
      <category>agents</category>
      <category>observability</category>
    </item>
  </channel>
</rss>
