This article provides a step by step architectural and deployment guide for StreamForge, a real-time event-driven multi-agent pipeline built with the Google Agent Development Kit (ADK), open-weights Gemma 3 (gemma3:12b), Google Cloud Eventarc, Cloud Pub/Sub, and Google Cloud Run.
Repository: github.com/RobinaMirbahar/streamforge-event-adk
Enterprise data platforms operate on real-time state changes: a product engineering team uploads a release specification (vertex-vector-sync-v4.2.json) to a Google Cloud Storage bucket, a streaming job commits a partition to BigQuery, or a partner service publishes a schema event to Cloud Pub/Sub. Connecting Google Cloud Eventarc directly to a multi-agent Google ADK workflow runtime on Cloud Run triggers autonomous workflows within 200 ms of an object landing in storage while scaling to 0 instances when the bucket is idle.
Why Combine Eventarc with ADK ParallelAgent and LoopAgent?
Processing a multi-regional product release requires three distinct tasks: extracting technical benchmarks from the raw JSON payload, translating launch messaging across target locales (en_US, de_DE, ja_JP), and simulating 12-month Annual Recurring Revenue (ARR) expansion under regional B2B currency rules. Running those three steps sequentially increases end-to-end latency, while running them in parallel without a verification step risks publishing unrounded foreign-exchange prices.
| Architecture Pattern | Trigger Mechanism | Execution Topology | Cross-Agent Consistency Check | Duplicate Event Protection |
|---|---|---|---|---|
| 🥇 Eventarc + ADK ParallelAgent & LoopAgent | Real-time CloudEvents v1.0 (< 200 ms lag) |
3 concurrent branches (3x fan-out) + LoopAgent critic
|
Iterative audit repairs EUR/JPY pricing until quality_score == 1.0
|
Firestore ce-id ledger short-circuits replays in < 5 ms
|
| 🥈 Scheduled ADK SequentialAgent | Fixed cron schedule | Linear 3-stage chain | Single-pass sequential handoff | Date-keyed Firestore lock |
| 🥉 Single Monolithic Prompt | Manual human chat prompt | Single LLM call | Unchecked single-pass output | Manual retry handling |
End-to-End Event-Driven Architecture on Google Cloud
-
Tier 1 — Event Ingestion and Routing: Uploading
releases/2026-10/vertex-vector-sync-v4.2.jsontogs://streamforge-launch-inboxemits a nativegoogle.cloud.storage.object.v1.finalizedevent. Google Cloud Eventarc wraps the notification in a CNCF CloudEvents v1.0 envelope (ce-id,ce-type,ce-source,ce-time) and delivers it over an OIDC-authenticated Cloud Pub/Sub push subscription. -
Tier 2 — Serverless Multi-Agent Runtime (Cloud Run): A private FastAPI receiver checks the incoming
ce-idheader (ce-98104271-gcs-finalized) against Cloud Firestore. New events execute the Google ADK workflow (ParallelAgent→LoopAgent→LlmAgent) powered by Gemma 3 12B viaLiteLlm. -
Tier 3 — State and Downstream Sink: The final publisher agent validates the output against the Pydantic
EventDrivenLaunchManifestschema and commits both the manifest (MANIFEST-SF-VECTOR-SYNC-4.2) and thece-iddeduplication record to Cloud Firestore.
Orchestrating ParallelAgent Fan-Out and LoopAgent Self-Correction
In Google ADK, all branches inside a ParallelAgent share the same InvocationContext.session.state. Assigning a distinct output_key to each branch isolates their writes cleanly:
-
TechnicalSpecExtractorAgentwrites engineering benchmarks tooutput_key="extracted_specs". -
GlobalLocalizationAgentwrites localized headlines and draft currency tiers tooutput_key="localized_copy". -
RevenuePricingSimulatorAgentwrites the 12-month ARR forecast and regional FX compliance rules tooutput_key="pricing_simulation".
Once all 3 parallel branches complete, QualityCritiqueRefinementLoop (LoopAgent with max_iterations=3) invokes LaunchReadinessCriticAgent to audit the combined state. When GlobalLocalizationAgent converts the $499/mo Growth Tier at the raw 0.92 EUR exchange rate (€459.08/mo), Iteration 1 of the LoopAgent flags the violation against the de_DE rule requiring VAT-exclusive B2B prices rounded to the nearest €5 (quality_score = 0.72) and updates the pricing tier to €460/mo. On Iteration 2, the audit reaches quality_score = 1.0 and invokes exit_quality_loop(), setting tool_context.actions.escalate = True to exit the loop immediately.
At This Point You Should Have
- Python
3.10+(3.11recommended) withgoogle-adk>=1.0.0,litellm,fastapi, andpydanticinstalled. - A Google Cloud project with
Cloud Run,Eventarc,Cloud Pub/Sub,Cloud Storage, andCloud FirestoreAPIs enabled. - Access to Gemma 3 12B (
ollama_chat/gemma3:12blocally via Ollama orgoogle/gemma-3-12b-iton a Cloud RunNVIDIA L424 GBGPU service).
Step 1 — Define the CloudEvent and Launch Manifest Schemas
The pipeline enforces typed contracts at both boundaries: CloudEventEnvelope validates the incoming Eventarc HTTP headers and payload, while EventDrivenLaunchManifest constrains the final ReleasePublisherAgent output in streamforge_agent/schemas.py.
from typing import Any, Dict, List
from pydantic import BaseModel, Field
class CloudEventEnvelope(BaseModel):
"""Standard CNCF CloudEvents v1.0 envelope delivered by Google Cloud Eventarc."""
ce_id: str = Field(description="Unique CloudEvent ID (ce-id header) used for idempotency.")
ce_type: str = Field(default="google.cloud.storage.object.v1.finalized")
ce_source: str = Field(description="CloudEvent source URI.")
ce_time: str = Field(description="ISO-8601 event timestamp.")
bucket: str = Field(description="GCS bucket name.")
object_name: str = Field(description="GCS object path that triggered the pipeline.")
generation: str = Field(description="GCS object generation number.")
class LoopCritiqueVerdict(BaseModel):
"""Iterative verification output produced inside ADK LoopAgent."""
iteration: int = Field(description="1-based LoopAgent iteration index.")
quality_score: float = Field(description="Composite readiness score from 0.0 to 1.0.")
discrepancies_found: List[str] = Field(description="Detected cross-agent inconsistencies.")
remediation_applied: str = Field(description="Correction applied during this loop iteration.")
escalate_exit: bool = Field(description="True when ToolContext.actions.escalate is triggered.")
class EventDrivenLaunchManifest(BaseModel):
"""Final validated launch package persisted to Cloud Firestore and BigQuery."""
manifest_id: str = Field(description="Deterministic manifest ID tied to release SKU.")
cloudevent_id: str = Field(description="Originating Eventarc ce-id.")
release_sku: str = Field(description="Product release SKU identifier.")
product_title: str = Field(description="Official release title.")
technical_highlights: List[str] = Field(description="Validated engineering specs and benchmarks.")
regional_launch_copy: Dict[str, str] = Field(description="Localized launch headlines (en_US, de_DE, ja_JP).")
pricing_tiers_usd_eur_jpy: Dict[str, str] = Field(description="Parity-verified multi-currency pricing.")
projected_net_arr_lift_usd: float = Field(description="Simulated 12-month ARR expansion in USD.")
loop_iterations_completed: int = Field(description="Number of LoopAgent self-correction passes.")
final_quality_score: float = Field(description="Final LoopAgent readiness score (1.0).")
generated_by_model: str = Field(description="Model identifier used by Google ADK.")
Step 2 — Implement the Parallel Enrichment and Loop Escalation Tools
In streamforge_agent/tools.py, each specialist tool exposes typed parameters and docstrings for Gemma 3 function calling, while exit_quality_loop sets tool_context.actions.escalate = True on the injected ADK ToolContext.
from typing import Any, Dict
def verify_cross_agent_launch_consistency(
draft_pricing_tiers: Dict[str, str],
iteration: int = 1,
) -> Dict[str, Any]:
"""Audits parallel agent outputs against regional pricing compliance and benchmark accuracy."""
eur_price = draft_pricing_tiers.get("EUR", "")
if "459.08" in eur_price and iteration == 1:
return {
"quality_score": 0.72,
"passed": False,
"discrepancies_found": [
"de_DE regional rule violation: EUR price '€459.08/mo' contains fractional cents instead of rounding to the nearest €5 (€460/mo VAT-excl.).",
"ja_JP regional rule check: JPY price '¥75,848/mo' should be rounded to clean commercial tier '¥75,800/mo'.",
],
"remediated_pricing_tiers": {
"USD": "Growth Tier: $499/mo (B2B Annual)",
"EUR": "Growth Tier: €460/mo (zzgl. MwSt.)",
"JPY": "Growth Tier: ¥75,800/月 (税抜)",
},
}
return {
"quality_score": 1.0,
"passed": True,
"discrepancies_found": [],
"remediated_pricing_tiers": draft_pricing_tiers,
}
def exit_quality_loop(tool_context: Any = None) -> Dict[str, Any]:
"""Signals Google ADK LoopAgent to terminate iteration once quality_score reaches 1.0."""
if tool_context is not None and hasattr(tool_context, "actions"):
tool_context.actions.escalate = True
return {"status": "LOOP_ESCALATED", "escalate": True}
Step 3 — Wire ParallelAgent, LoopAgent, and SequentialAgent
In streamforge_agent/agent.py, build_streamforge_event_pipeline() composes the 3 parallel enrichment agents, the iterative quality critic loop, and the schema-constrained publisher agent into a single parent SequentialAgent.
import os
from google.adk.agents import LlmAgent, LoopAgent, ParallelAgent, SequentialAgent
from google.adk.models.lite_llm import LiteLlm
from .schemas import EventDrivenLaunchManifest
from .tools import (
exit_quality_loop,
generate_regional_localization_bundle,
parse_gcs_release_artifact,
publish_launch_manifest_to_firestore,
simulate_tier_pricing_and_arr,
verify_cross_agent_launch_consistency,
)
def build_streamforge_event_pipeline() -> SequentialAgent:
"""Constructs the hybrid ParallelAgent -> LoopAgent -> ReleasePublisherAgent ADK workflow."""
model = LiteLlm(model=os.getenv("STREAMFORGE_MODEL_NAME", "ollama_chat/gemma3:12b"))
spec_extractor = LlmAgent(
name="TechnicalSpecExtractorAgent",
model=model,
instruction=(
"Call `parse_gcs_release_artifact` for the incoming bucket and object_name and store "
"the extracted engineering benchmarks in `{extracted_specs}`."
),
tools=[parse_gcs_release_artifact],
output_key="extracted_specs",
)
localization_agent = LlmAgent(
name="GlobalLocalizationAgent",
model=model,
instruction=(
"Call `generate_regional_localization_bundle` and write localized copy and draft currency "
"tiers to `{localized_copy}`."
),
tools=[generate_regional_localization_bundle],
output_key="localized_copy",
)
pricing_simulator = LlmAgent(
name="RevenuePricingSimulatorAgent",
model=model,
instruction=(
"Call `simulate_tier_pricing_and_arr` for the release SKU and write the ARR forecast "
"and FX rounding rules into `{pricing_simulation}`."
),
tools=[simulate_tier_pricing_and_arr],
output_key="pricing_simulation",
)
parallel_fanout = ParallelAgent(
name="ConcurrentEnrichmentFanOut",
description="Runs spec extraction, localization, and ARR simulation concurrently.",
sub_agents=[spec_extractor, localization_agent, pricing_simulator],
)
readiness_critic = LlmAgent(
name="LaunchReadinessCriticAgent",
model=model,
instruction=(
"Inspect `{extracted_specs}`, `{localized_copy}`, and `{pricing_simulation}`. "
"Call `verify_cross_agent_launch_consistency`. Once `quality_score` reaches 1.0, "
"call `exit_quality_loop` to set `tool_context.actions.escalate = True`."
),
tools=[verify_cross_agent_launch_consistency, exit_quality_loop],
output_key="critique_verdict",
)
quality_loop = LoopAgent(
name="QualityCritiqueRefinementLoop",
sub_agents=[readiness_critic],
max_iterations=3,
)
release_publisher = LlmAgent(
name="ReleasePublisherAgent",
model=model,
instruction=(
"Read `{extracted_specs}`, `{localized_copy}`, `{pricing_simulation}`, and "
"`{critique_verdict}` from session state. Call `publish_launch_manifest_to_firestore` "
"and return the final `EventDrivenLaunchManifest`."
),
tools=[publish_launch_manifest_to_firestore],
output_key="published_manifest",
output_schema=EventDrivenLaunchManifest,
)
return SequentialAgent(
name="StreamForgeEventDrivenPipeline",
sub_agents=[parallel_fanout, quality_loop, release_publisher],
)
Step 4 — Verify the Pipeline and CloudEvent Deduplication Locally
Run scripts/simulate_cloudevent_pipeline.py to execute the 3-branch ParallelAgent fan-out, inspect the 2-iteration LoopAgent self-correction trace, and verify the ce-id idempotency guard against a duplicate Pub/Sub delivery.
python scripts/simulate_cloudevent_pipeline.py
========================================================================================
STREAMFORGE: EVENT-DRIVEN ADK (PARALLELAGENT + LOOPAGENT) PIPELINE VERIFICATION
Trigger: Eventarc CloudEvent (google.cloud.storage.object.v1.finalized) | Gemma 3 12B
========================================================================================
[STEP 1] Initialized Google ADK Hybrid Agent Graph:
* Stage 1 — Concurrent Fan-Out: ParallelAgent (ConcurrentEnrichmentFanOut)
* Stage 2 — Self-Correcting Quality Loop: LoopAgent (QualityCritiqueRefinementLoop)
* Stage 3 — Validated Sink Publisher: LlmAgent (ReleasePublisherAgent)
----------------------------------------------------------------------------------------
[STEP 2] Delivering Eventarc CloudEvent #1 (POST /api/v1/cloudevent)
ce-id: ce-98104271-gcs-finalized | object: gs://streamforge-launch-inbox/releases/2026-10/vertex-vector-sync-v4.2.json
----------------------------------------------------------------------------------------
Ack Status : ACK_PROCESSED
Parallel Branches Executed : 3 (SpecExtractor || Localization || PricingSim)
LoopAgent Critique Passes : 2
Firestore Manifest ID : MANIFEST-SF-VECTOR-SYNC-4.2
--- Stage 2 LoopAgent Self-Correction Trace (`session.state['loop_critique_trace']`) ---
[
{
"iteration": 1,
"quality_score": 0.72,
"discrepancies_found": [
"de_DE regional rule violation: EUR price '€459.08/mo' contains fractional cents instead of rounding to the nearest €5 (€460/mo VAT-excl.).",
"ja_JP regional rule check: JPY price '¥75,848/mo' should be rounded to clean commercial tier '¥75,800/mo'."
],
"remediation_applied": "Re-formatted de_DE Euro tier to €460/mo (zzgl. MwSt.) and ja_JP Yen tier to ¥75,800/月 to comply with regional B2B purchasing power rules in session.state['pricing_simulation'].",
"escalate_exit": false
},
{
"iteration": 2,
"quality_score": 1.0,
"discrepancies_found": [],
"remediation_applied": "All 3 parallel branches verified consistent. Triggered exit_quality_loop().",
"escalate_exit": true
}
]
--- Stage 3 Validated Launch Manifest (`session.state['published_manifest']`) ---
{
"manifest_id": "MANIFEST-SF-VECTOR-SYNC-4.2",
"cloudevent_id": "ce-98104271-gcs-finalized",
"release_sku": "SF-VECTOR-SYNC-4.2",
"product_title": "StreamForge Real-Time Vector & Iceberg Lakehouse Sync v4.2",
"technical_highlights": [
"Reduced p99 vector sync latency by 73.8% (from 145ms down to 38ms).",
"Sustains 250,000 rows/sec streaming throughput with 4.6x zero-copy delta compression.",
"Native connectors for BigQuery, Apache Iceberg, AlloyDB, Cloud Spanner."
],
"regional_launch_copy": {
"en_US": "StreamForge Real-Time Vector & Iceberg Lakehouse Sync v4.2: Sub-40ms streaming sync across BigQuery, Iceberg, and AlloyDB with 4.6x zero-copy compression.",
"de_DE": "StreamForge Echtzeit-Vektor- & Iceberg-Sync v4.2: 38 ms p99-Latenz für BigQuery und AlloyDB bei 4,6-facher Zero-Copy-Komprimierung.",
"ja_JP": "StreamForge リアルタイム Vector & Iceberg 同期 v4.2:BigQuery・AlloyDB 向けに p99 レイテンシ 38ms と 4.6倍のゼロコピー圧縮を実現。"
},
"pricing_tiers_usd_eur_jpy": {
"USD": "Growth Tier: $499/mo (B2B Annual)",
"EUR": "Growth Tier: €460/mo (zzgl. MwSt.)",
"JPY": "Growth Tier: ¥75,800/月 (税抜)"
},
"projected_net_arr_lift_usd": 2856000.0,
"loop_iterations_completed": 2,
"final_quality_score": 1.0,
"generated_by_model": "ollama_chat/gemma3:12b"
}
----------------------------------------------------------------------------------------
[STEP 3] Simulating Pub/Sub At-Least-Once Duplicate Delivery (Same `ce-id`)
Verifying CloudEvent Deduplication Ledger (`cloudevent_dedup_ledger`)
----------------------------------------------------------------------------------------
Ack Status : ACK_DUPLICATE_CLOUDEVENT
Idempotent Replay : True
Parallel Branches Executed : 0 (Zero redundant GPU inference)
Deduplication Metadata : {"cloudevent_dedup_hit": true, "ce_id": "ce-98104271-gcs-finalized"}
========================================================================================
Step 5 — Deploy the Private Cloud Run Receiver and Eventarc Trigger
Run deploy/deploy_eventarc_adk.sh to deploy the container to Google Cloud Run (2 vCPU, 2 GiB memory, --no-allow-unauthenticated, --min-instances=0, --max-instances=10) and bind the Eventarc trigger to google.cloud.storage.object.v1.finalized.
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
export GOOGLE_CLOUD_LOCATION="us-central1"
bash deploy/deploy_eventarc_adk.sh
==> [1/6] Enabling Cloud Run, Eventarc, Pub/Sub, Storage, and Firestore APIs...
==> [2/6] Creating Cloud Storage Inbox Bucket & Dead-Letter Pub/Sub Topic...
==> [3/6] Creating Least-Privilege Service Accounts...
==> [4/6] Deploying StreamForge ADK Service to Cloud Run (private ingress)...
Service [streamforge-event-adk] revision [streamforge-event-adk-00001-x8k] has been deployed and is serving 100 percent of traffic.
==> [5/6] Granting Cloud Run Invoker + Eventarc EventReceiver to Eventarc SA...
==> [6/6] Creating Eventarc Trigger for google.cloud.storage.object.v1.finalized...
Created trigger [streamforge-gcs-release-trigger] in [us-central1].
Official Google Cloud and ADK Documentation
- Google Agent Development Kit (ADK) Documentation
- Google ADK Workflow Agents (
SequentialAgent,ParallelAgent,LoopAgent) - Google ADK Python SDK Repository (
google/adk-python) - Google Gemma Open Models Documentation
- Google Cloud Eventarc Documentation
- Google Cloud Run Documentation
- Google Cloud Pub/Sub Push Subscriptions Guide
- Google Cloud Firestore Documentation
Summary
The goal of this article was to build and verify a real-time, event-driven multi-agent pipeline on Google Cloud using Eventarc, Cloud Pub/Sub, Cloud Run, open-weights Gemma 3, and the Google Agent Development Kit. The key to the solution was pairing ParallelAgent for concurrent 3-branch fan-out with LoopAgent for iterative cross-branch self-correction and a Cloud Firestore ce-id deduplication ledger. The verification results were:
- 🟢 Concurrent Fan-Out (
ParallelAgent): Extracted engineering specs (73.8%latency drop from145 msto38 msat250,000rows/sec), localized copy across3locales (en_US,de_DE,ja_JP), and forecasted$2,856,000in 12-month Net New ARR lift across340upgraded accounts (18.5%of1,840active Growth Tier accounts at$8,400annual expansion). - 🟢 Self-Correcting Quality Loop (
LoopAgent): Detected the€459.08/moand¥75,848/mocurrency formatting violations on Iteration1(quality_score = 0.72), repaired them to€460/moand¥75,800/mo, and exited cleanly on Iteration2(quality_score = 1.0) viaexit_quality_loop(). - 🟢 At-Least-Once Idempotency: Short-circuited duplicate
ce-98104271-gcs-finalizedredelivery in< 5 mswith0redundant parallel branches or LLM calls.
The strategy for orchestrating event-driven multi-agent pipelines with Google ADK and Gemma 3 on Cloud Run was validated with an incremental step by step approach.


Top comments (0)