{"slug": "what-100-test-coverage-missed-state-across-google-adk-a2a-boundaries", "title": "What 100% Test Coverage Missed: State Across Google ADK A2A Boundaries", "summary": "A developer building Bastion, a three-agent access-governance fleet with Google ADK and A2A, discovered that state written via `output_key` does not cross remote A2A boundaries, causing silent failures in deployed workflows despite 100% test coverage. The issue is documented in google/adk-python#6854, and a community fix by Sylvester Kaczmarek is pending in PR #6859.", "body_md": "*I created this article for the purpose of entering the All Things Agentic Hackathon.*\n\n**TL;DR** — An ADK `output_key`\n\nwrites into the session of the agent that *declares* it. In-process that session is shared, so it looks like state flows. Across a `RemoteA2aAgent`\n\nhop it is the worker's session, and it never comes back. Nothing raises. Nothing warns. Every local run and every CI job exercises the working topology, so the failure is invisible to an offline test suite **by construction** — including at 100% coverage.\n\nBastion is a three-agent access-governance fleet built with Google ADK and A2A. An Orchestrator owns investigation state, an Access Auditor reads production IAM through a read-only identity, and a model-free Escalation Agent delivers validated count-only reviews.\n\nThe local graph passed its configured core statement and branch coverage gate. Every branch, every seam.\n\nThen the same graph was split across deployed A2A workers, and an assumption that looked natural in-process became false.\n\nIn-process, the previous step's result is simply *there*:\n\n```\n# The Auditor declares output_key; the Orchestrator reads it back.\nreport = ctx.session.state.get(AUDIT_FINDINGS_KEY)\n```\n\nDeploy the same sequence and only the construction changes. The graph is identical:\n\n```\nRemoteA2aAgent(\n    name=\"access_auditor\",\n    agent_card=card_url(auditor, \"access_auditor\"),\n    description=\"Reads the live IAM policy and flags anomalies. Read-only.\",\n    httpx_client=private_a2a_client(auditor),\n    a2a_request_meta_provider=_forward_investigation,\n)\n```\n\n`output_key`\n\nstill writes. It writes into the **worker's** session, which never crosses back. The deployed Orchestrator saw an empty state key while every local run and every test saw a populated one.\n\nObserved 2026-08-22: the Auditor completed a full sub-trail, and the next step then refused with *\"returned no structured report.\"* No exception at the boundary. No warning at construction. The run still reported `completed`\n\n.\n\nTwo directions are easy to confuse:\n\n`output_key`\n\ninto its own session without making that state available to the caller.Neither requires a crash. Plausible older content can remain in the caller and make the workflow look successful while the new state never crossed. That is far more dangerous than an obvious exception — a silent wrong answer only beats a loud failure in the sense that it survives longer.\n\nBastion stopped treating remote session state as transport.\n\nThe authoritative findings now cross the boundary as structured, validated message content. Reading the reply is sound rather than a workaround **because** of `output_schema`\n\n: the Auditor's final content is validated JSON, not prose the caller has to interpret. Anything that fails to parse is skipped rather than guessed at, and an empty result still fails closed downstream.\n\nThe caller then verifies that content field by field against the deterministic tool output — IDs, categories, departments, scores, and required routing must all match. A model may supply bounded rationale. It cannot author or alter the finding set.\n\nCorrelation uses durable request metadata rather than model-visible prose. `invocation_id`\n\ngroups one ADK run and stops at the A2A boundary; `investigation_id`\n\nis carried as request metadata and joins the complete distributed flow, so no model reads or restates it.\n\nCompletion is explicit too: the Orchestrator cannot mark an investigation done until every required department delivery has a validated receipt.\n\nThe result is not a workaround that copies more state. It is a service-boundary contract.\n\nThe deployed reproducer and both loss directions are in [google/adk-python#6854](https://github.com/google/adk-python/issues/6854), which is **open**.\n\nA community change, [#6859](https://github.com/google/adk-python/pull/6859) by Sylvester Kaczmarek, addresses the state-only outbound loss and the rejected inbound remote-state-delta case. It is **open and unmerged**.\n\nA second Google collaborator later added that the `output_key`\n\nhalf is expected behaviour — remote sessions are per-agent by design. **That is true, and it was never the complaint.** The report asked for the silent boundary to become observable, not for the semantics to change. They also asked for a reproduction to test #6859, which I supplied the same day.\n\nI also opened [#6862](https://github.com/google/adk-python/pull/6862), adding a construction-time warning for the adjacent `LlmAgent(output_key) -> RemoteA2aAgent`\n\npattern. A Google maintainer closed it unmerged on 2026-08-24, and the reply is the most useful artifact this whole exercise produced:\n\nThe session state boundary across A2A is a general transport property rather than an issue specific to\n\n`SequentialAgent`\n\n. It applies equally to`Workflow`\n\n, custom agents, and standalone runs. This is already being addressed centrally at the`RemoteA2aAgent`\n\nconverter and documentation layer in #6859.\n\nTwo further reasons were given: `SequentialAgent`\n\nis deprecated in favour of `Workflow`\n\n, so new domain-specific validation should not be added to it; and my `sys.modules.get`\n\nreflection check was import-order sensitive and could silently skip validation. The last one is simply a correct review comment.\n\nSo the honest scoreboard: the boundary is confirmed by a maintainer and being addressed centrally, the issue is open, and my patch was closed as the wrong place to fix it.\n\nThat is a better outcome than a merged patch in a deprecated component — and it is **not** the same thing as \"Google accepted our fix.\" Nothing here is merged. Check the live state before you repeat any of it.\n\nTreat remote agent state exactly like state across any other service boundary:\n\nOne hundred percent configured coverage can prove every local branch ran. It cannot prove the deployment topology preserved an assumption the local topology made invisible.\n\nThat distinction is the whole article. Coverage measures what your tests execute, not what your architecture assumes.\n\nLong-running agents remember approvals, retries, leases, exceptions, and delivery receipts. If that context crosses departments or identities *implicitly*, the system cannot explain who owned a decision or whether it arrived.\n\nFor an access-governance fleet that is not a bug class, it is a compliance failure: \"the state was there locally\" is not an audit trail.\n\nAn explicit state boundary makes the fleet easier to secure, audit, recover, and extend — and it is the difference between an agent system you can deploy and one you can only demo.\n\n**Bastion** — repository, evidence, and demo:", "url": "https://wpnews.pro/news/what-100-test-coverage-missed-state-across-google-adk-a2a-boundaries", "canonical_source": "https://dev.to/arjunganesh/what-100-test-coverage-missed-state-across-google-adk-a2a-boundaries-29i1", "published_at": "2026-08-29 21:15:49+00:00", "updated_at": "2026-08-29 21:49:02.915832+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-infrastructure"], "entities": ["Google ADK", "A2A", "Bastion", "Sylvester Kaczmarek", "google/adk-python"], "alternates": {"html": "https://wpnews.pro/news/what-100-test-coverage-missed-state-across-google-adk-a2a-boundaries", "markdown": "https://wpnews.pro/news/what-100-test-coverage-missed-state-across-google-adk-a2a-boundaries.md", "text": "https://wpnews.pro/news/what-100-test-coverage-missed-state-across-google-adk-a2a-boundaries.txt", "jsonld": "https://wpnews.pro/news/what-100-test-coverage-missed-state-across-google-adk-a2a-boundaries.jsonld"}}