My Agent Returned Success. The Browser State Said Otherwise

My Agent Returned Success. The Browser State Said Otherwise
View on original source
Category: Business
Share
Archive
Like
An agent clicks Checkout. The browser tool returns success . The run completes without an exception. But the page is still the cart. Which result should the test trust? This is one of the most dangerous green states in agent development: the action succeeded, but the outcome did not. A browser driver can accept a click without causing navigation. A queue can acknowledge a message that a consumer later rejects. A database client can resolve before a downstream projection updates. If your test ends at the tool's return value, you are testing the transport—not the task. Success exists at three layers Consider three claims: The tool function returned normally. The agent run completed normally. The expected state transition occurred. The first two are execution evidence. The third is outcome evidence. All three are useful, but they are not interchangeable. agent intent | v tool call ---------> { status: "success" } | | independent read v browser state -----> { page: "cart" } | v observed outcome: failed Enter fullscreen mode Exit fullscreen mode That last read closes a gap that ordinary happy-path tracing can leave open. Record the contradiction, not just the exception While maintaining AgentInspect, I added a synthetic recipe for this failure. The important idea is portable: perform the action, then observe the world through a separate surface. This example is verified against agent-inspect@6.17.6 : import { inspectRun , observeOutcome , step } from " agent-inspect " ; const browserState = { page : " cart " }; await inspectRun ( " checkout-agent " , async () => { const before = { ... browserState }; const action = await step . tool ( " browser.clickCheckout " , async () => { // Synthetic bug: the tool reports success, // but the page never changes. return { status : " success " as const }; }, ); const after = { ... browserState }; const transitioned = before . page === " cart " && after . page === " checkout " ; await observeOutcome ( " checkout-transition " , { expectation : " Page changed from cart to checkout " , status : transitioned ? " passed " : " failed " , method : " snapshot " , actual : { beforePage : before . page , afterPage : after . page , }, evidence : { actionStatus : action . status , }, }); }, { traceDir : " .agent-inspect " , silent : true , }, ); Enter fullscreen mode Exit fullscreen mode The run status is success . The observed outcome is failed . Both statements are true, and preserving both is far more useful than forcing them into one status. Make the mismatch visible in review Inspect only the observations section: npx agent-inspect report \ --dir .agent-inspect \ --section observations Enter fullscreen mode Exit fullscreen mode ## Observed outcomes Total: 1 (passed 0, failed 1, unknown 0, skipped 0) | Name | Status | Expectation | Method | | ------------------- | ------ | ----------------------------------- | -------- | | checkout-transition | failed | Page changed from cart to checkout | snapshot | Enter fullscreen mode Exit fullscreen mode Then make the same evidence fail CI: npx agent-inspect check \ --dir .agent-inspect \ --fail-on-observation failed Enter fullscreen mode Exit fullscreen mode The check exits nonzero even though the agent run completed. You can also find this class of run later: npx agent-inspect search \ --dir .agent-inspect \ --observation failed Enter fullscreen mode Exit fullscreen mode This is the difference between recording an interesting fact and making that fact operational. Choose an observer outside the action The strongest observer reads a different surface from the one that performed the action. Action Better observation Browser click DOM, accessibility tree, or URL snapshot Database write Independent read by business key Queue publish Consumer receipt or downstream state File generation File existence plus a content or digest check API mutation Follow-up GET or emitted domain event If clickCheckout() performs the click and verifies success using its own internal didClick flag, the test can repeat the same mistake twice. Independence matters more than sophistication. For eventually consistent systems, the observation may need a bounded polling policy: async function eventually < T > ( read : () => Promise < T > , accept : ( value : T ) => boolean , attempts = 5 , ): Promise < T > { let value = await read (); for ( let i = 1 ; i < attempts && ! accept ( value ); i ++ ) { await new Promise (( resolve ) => setTimeout ( resolve , 200 )); value = await read (); } return value ; } Enter fullscreen mode Exit fullscreen mode The timeout, interval, and accepted state should come from the product's consistency contract—not from whatever delay makes today's test pass. Define 'done' before you instrument it Outcome checks become much sharper when the team writes the completion condition before choosing a selector or API call. For checkout, 'the button was clicked' is an implementation detail. 'The browser reached the checkout route and displayed the expected order context' is closer to a product outcome. For a support agent, 'the ticket API returned 201' may still be weaker than 'the created ticket is visible by its business key with the expected queue and priority.' A small outcome contract keeps that distinction reviewable: type OutcomeContract < T > = { name : string ; expectation : string ; observe : () => Promise < T > ; passed : ( value : T ) => boolean ; }; Enter fullscreen mode Exit fullscreen mode The contract does not need to be a framework. Its value is forcing the action and the proof of completion to be named separately. Keep the evidence smaller than the incident actual and evidence should contain the smallest proof that explains the result: status, field presence, counts, digests, or safe identifiers. For this case, beforePage , afterPage , and actionStatus are sufficient. A full HTML document, screenshot sequence, customer record, prompt, or browser session is not. AgentInspect bounds and redacts recorded values before disk, but that is not permission to capture everything. Collection policy comes first; redaction is defense in depth. What this pattern does not prove An observation is only as trustworthy as its observer. A stale replica may report failure after a successful write. A DOM selector may target the wrong element. A passed observation also does not prove the whole workflow was safe. This is post-execution evidence, not a runtime guardrail. Permissions, idempotency, budgets, and destructive-action controls still belong in the application and tool gateway. Try the failure without a browser or model key The pinned browser/MCP observed-outcome recipe uses only in-memory state. It deliberately returns tool success while leaving the simulated page on cart . That makes it a useful five-minute exercise: run it, inspect the failed observation, then replace the synthetic snapshot with one state read from your own system. The conceptual change is small but consequential: stop asking only whether the agent ran. Ask which independent state would prove that the work happened. What surface would you trust after your agent says done?

(0)Comments

 

A note on cookies

Newshunt uses essential cookies to keep you signed in and to remember your language and country, so the site works the way you expect. With your permission, we'd also like to use analytics cookies to understand how people use Newshunt and improve it over time.

Accepting only affects analytics. To learn more, view our Privacy Policy or Terms & Conditions.