Architecture: From Shared Filesystem to A2A
The problem
Section titled “The problem”In the local system, all agents share an output directory. Each agent writes its decisions to files and the next one reads them directly:
OUTPUT_DIR = os.getenv('ADK_OUTPUT_DIR', '/app/outputs')This works because they all run in the same Docker container with access to the same filesystem. On Cloud Run, each service has its own isolated container — there is no shared /app/outputs/.
Why no shared filesystem on Cloud Run
Section titled “Why no shared filesystem on Cloud Run”Options like GCS FUSE or Filestore NFS exist for mounting shared volumes. However, Google does not recommend them for inter-service communication. The correct pattern in serverless architectures is: state travels in the messages, not in the filesystem.
The solution: A2A protocol
Section titled “The solution: A2A protocol”Agent-to-Agent (A2A) is an open protocol under the Linux Foundation. Each agent exposes itself as an independent HTTP server with its own URL. Communication is via JSON messages — the orchestrator sends a task and receives a response.
Accumulated context
Section titled “Accumulated context”The orchestrator chains the 7 agents and accumulates context from each one. Each agent receives everything that the previous ones generated:
Orchestrator │ ├──→ Platform Architect (receives: task) │ ├──→ Infrastructure (receives: task + PA context) │ ├──→ Security (receives: task + PA + Infra context) │ ├──→ CI/CD (receives: task + PA + Infra + Sec context) │ ├──→ Observability (receives: task + PA + Infra + Sec + CICD) │ ├──→ DevEx (receives: task + PA + Infra + Sec + CICD + Obs) │ └──→ Web Portal (receives: task + all 6 previous)The context grows with each step. Web Portal receives the accumulated output from all 6 previous agents to build the portal with the full IDP information.
The key refactor
Section titled “The key refactor”The main change is in how each agent obtains context from the previous ones.
Before (local system) — reads from disk:
def get_platform_config() -> dict: """Reads platform-config.yaml from the shared filesystem.""" config_path = os.path.join(OUTPUT_DIR, 'platform-config.yaml') with open(config_path) as f: return yaml.safe_load(f)After (Cloud Run) — reads from the message, with disk fallback:
def get_platform_config(context_json: str = "") -> dict: """Reads context from the A2A message. Falls back to disk if empty.""" if context_json: return json.loads(context_json) # Fallback for local execution config_path = os.path.join(OUTPUT_DIR, 'platform-config.yaml') with open(config_path) as f: return yaml.safe_load(f)This pattern is repeated in 6 of the 7 agents. Platform Architect doesn’t need it because it’s the first in the chain — it has no previous context to receive.
Next step: The 7 Agents — Dockerfile, agent.json and tools →