Scaling agentic AI on Google Cloud: How AppsFlyer built a governed multi-agent platform

Authors: Michael Gadaev, Roy Ninio(AppsFlyer)
In the data and analytics space, the most common question I hear from enterprise customers today is: “How do we scale AI without losing control over our data?” Building a single AI agent in a silo is easy; building a governed platform that safely orchestrates 30,000 agents daily across an organization is incredibly hard.

The team at AppsFlyer found a brilliant answer. By bridging Google Cloud’s foundational data layer (BigQuery, AlloyDB) with our serverless AI stack (Vertex AI, Cloud Run), they solved the hardest problems in enterprise GenAI adoption: governance, security, and scale.

I am proud to host this guest post by Roy Ninio, AI Platform Team Leader at AppsFlyer, where he breaks down exactly how they achieved this architectural feat.

Here is their story:

Enterprises are shifting from standalone AI assistants to agentic platforms — systems where dozens of specialized agents collaborate over real business data. At AppsFlyer, we hit the hard problems early, because we lived them: agents were multiplying as silos. Every team built its own runtime, deployment, permissions logic, and UI. Capabilities couldn’t be shared, governance couldn’t be enforced, and nobody could answer “which agents do we even have?”

To solve this, we built an end-to-end agentic AI platform on Google Cloud, with Agent Hub as its product surface. The core bet: an agent should contribute only its ability — everything else is the platform’s job. Deployment, runtime, scale, configuration, user restrictions, discovery, UI, delivery, and observability are handled once, centrally, for every agent. An agent that joins is completely governed by the platform, and in return its capability becomes discoverable and shareable across the entire company through its agent card.


The full architecture. Numbered badges match the five orchestration stages; purple is the A2A protocol.

Agent Hub: A UI that builds itself from agent cards

Agent Hub never hardcodes its interface — the UI is a projection of the platform’s discovery API. Every agent publishes an agent card, the A2A-standard descriptor of who it is and what it can do:

Agent card - JSON
{
  "protocolVersion": "0.3.0",
  "name": "cohort-analysis-agent",
  "description": "Retention, LTV, and engagement cohorts across AppsFlyer data products.",
  "url": "https://agents.example.com/cohort-analysis/a2a/v1",
  "version": "2.4.1",
  "capabilities": {
    "streaming": true,
    "extensions": [
      { "uri": "https://a2ui.org/a2a-extension/a2ui/v0.9",
        "description": "Emits declarative A2UI components as task artifacts", "required": false },
      { "uri": "https://extensions.example.com/governance/v1",
        "description": "Propagates user-restriction context on every task", "required": true }
    ]
  },
  "skills": [
    { "id": "retention-cohorts",
      "name": "Retention cohort analysis",
      "description": "Dn retention cohorts by app, date range, and segmentation.",
      "tags": ["retention", "cohorts", "analytics"],
      "examples": ["D1/D7/D30 retention for com.example.game by media source, last 90 days"] }
  ]
}

Three fields do real work: skills is what the orchestrator matches tasks against — discovery is semantic matching, not a routing table; extensions declares what the agent speaks beyond baseline A2A; and url is the agent’s A2A endpoint: the platform integrates with the agent through nothing but requests to this address — no shared code, no SDK, no direct integration. Because the connection is just a URL, an agent hosted anywhere, built on any framework, can join the platform as a remote agent (more on that below).

From these cards, Agent Hub renders everything at runtime: zero-deployment onboarding (register a card, appear in the catalog), consent and ownership (account-level opt-in, named admins), dynamic results via A2UI (each agent emits declarative UI component trees as task artifacts — charts, tables, reports — rendered safely client-side with zero agent-authored frontend code), and a trajectory explorer for asking questions about agent runs themselves (more below).

The platform contract: The end of silo agents

The expensive part of an agent is never the agent — it’s everything around it. So the platform owns that “everything”: deployment (one paved road), runtime (managed, on Cloud Run, with uniform logging and tracing), scale (autoscaling is a platform property), configuration (models, prompts, and tools versioned platform-side), and user restrictions (resolved and enforced on every request — an agent physically cannot opt out). In exchange, the agent’s ability is published via its card and becomes composable into any workflow. Ten teams solving deployment ten times became ten abilities the whole company shares.

Cloud Run is what makes the runtime side of this contract cheap to keep. Agentic workloads are violently uneven: the platform can sit idle for long stretches, then a single request fans out into dozens of parallel A2A calls. Cloud Run’s serverless model scales to zero between bursts and reacts instantly to spikes, containers are a natural fit for packaging ADK-based Python agents, and metrics, tracing, and logging come out of the box — the team operates a platform, not infrastructure.

Agents are built with the Google Agent Development Kit (ADK), whose strengths map directly onto platform needs: code-first Python agents (what makes the “blueprint” model real), native multi-agent composition (the foundation of our orchestration), built-in session and state management, tool and lifecycle callbacks (the exact interception points where centralized guardrails hook in), and out-of-the-box A2A support with clean Cloud Run deployment. In practice, this is what tamed the chaos: building one agent is easy, but getting dozens of agents to follow the same rules is hard — with ADK, a developer writes only the skill logic, and the framework’s lifecycle callbacks intercept every tool call to apply the platform’s restriction policies automatically. Models are a configuration concern too: agents run Gemini models served through Agent Platform (Vertex AI) , so upgrading or mixing models per agent is a platform-side change, not a code change. ADK gives us the how of building an agent; the platform decides the where, the who-may, and the how-much.

A2A: One protocol, extensible by design

Agents communicate exclusively over the Agent-to-Agent (A2A) protocol — no side channels, no bespoke integrations. A2A supplies the primitives a multi-agent platform lives on: agent cards for capability advertisement, task semantics for long-running work, and structured message and artifact exchange between agents that share no code.

Just as important, A2A is extensible. Extensions are declared in the agent card and activated per request — the client lists the extension URIs it wants, the server confirms which are active:

A2A extension negotiation — HTTP:

POST /cohort-analysis/a2a/v1  HTTP/1.1
X-A2A-Extensions: https://a2ui.org/a2a-extension/a2ui/v0.9,
                  https://extensions.example.com/governance/v1

{ "jsonrpc": "2.0", "method": "message/send", "params": { ... } }

Two extension patterns carry most of our platform semantics. A2UI rides as a data extension: agents attach UI trees to artifacts; clients that negotiated it render rich components, clients that didn’t still get the plain result. Governance rides as a required extension: every task carries the resolved user-restriction context as extension metadata, and the platform rejects tasks that arrive without it. That’s how a protocol enforces a policy — new semantics ship as extensions while baseline interoperability is never sacrificed.

This is also what makes remote agents trivial. A developer with an agent on any framework : LangGraph, CrewAI, homegrown — joins the platform in two steps: expose an A2A server, register the agent card. From that moment it’s discoverable, orchestratable, visible in Agent Hub, and governed like any native agent. The protocol is the membership test, not the framework.

Governance, scheduling, and delivery

Every request passes through the governance backend: authorization resolves each call to a user identity (agents never hold standing credentials — they act strictly within the requesting user’s authority); per-user restrictions are stored centrally, and downstream components fetch them themselves rather than trusting callers to pass them along, so a forgotten parameter can’t become a data leak; and guardrails — input validation, output filtering, scope enforcement — apply uniformly, protecting even agents not yet written.

Agents are untrusted by default. Safety is a property of the platform, not of agent-author diligence.

Two more services turn interactive agents into autonomous routines. The scheduling service makes any agent schedulable — at trigger time it invokes the platform under the user’s identity and restrictions, so a scheduled run can never see more than its creator. The notification service delivers output where users actually work: Slack or email, A2UI components included.

The orchestration agent: Plan before you act

The brain of the platform is the orchestration agent, an ADK agent on Cloud Run whose job is not to answer questions but to turn intent into a governed execution plan:

Orchestrator — Python (ADK):

orchestrator = LlmAgent(
    name="orchestrator",
    model=PLANNER_MODEL,
    instruction=ORCHESTRATION_PROMPT,   # intent -> grounded plan -> task graph
    tools=[
        semantic_layer_lookup,   # ground intent in AppsFlyer business context
        discovery_search,        # match required skills against agent cards
        build_task_graph,        # decompose into sequential/parallel branches
        dispatch_a2a_task,       # execute a node as an A2A task
    ],
    before_tool_callback=enforce_guardrails,   # platform interception point
    after_agent_callback=record_trajectory,    # every step -> trajectory layer
)

A request flows through five stages:
1) intent:parse the request and build an explicit chain of thought, persisted as a first-class object;
2) grounding:consult the semantic layer before choosing any agent, so plans are made only in terms the business actually has (planning without grounding is how orchestrators hallucinate capabilities);
3) discovery:match the plan’s required skills against registered agent cards, native or remote;
4) task breakdown:emit a task graph and decide per branch, at plan time, whether execution is sequential (one agent’s output feeds another) or parallel;
5) execution:dispatch each node as an A2A task, with status and artifacts streaming back into the response and the trajectory record.

The unit of capability is the agent blueprint: a self-contained implementation of one specific ability — cohort analysis, anomaly detection, report generation — published once, reusable by any workflow. Teams don’t build pipelines; they contribute abilities, and the orchestrator assembles them per request.

The data and semantic layers: BigQuery, sealed behind ChaseSQL

All analytical data lives in BigQuery, but no agent writes SQL against it directly. Every access flows through ChaseSQL, our smart query builder, inspired by the CHASE-SQL research line.
It receives grounded intent and generates three candidate queries via three independent strategies:
1.direct text-to-SQL
2.query-plan reasoning (the model first reasons like a database engine — scans, joins, filters — then derives the SQL, strongest on complex aggregations)
3.divide-and-conquer (decompose into sub-questions, solve as fragments, compose; strongest on nested multi — part questions).
Three reasoning paths, then selection of the best, is what lifts accuracy on the hard tail — where a single-strategy generator fails silently and confidently.

Just as important is what ChaseSQL refuses to do: it fetches the requesting user’s restrictions itself and enforces them inside query construction — restricted tables, columns, and row scopes are excluded before the SQL exists, not filtered afterward. Data access is completely sealed; no upstream mistake can produce an over-privileged query. And the sealing is defense-in-depth: beneath ChaseSQL, BigQuery’s own row- and column-level security acts as a hardcoded safety net, while its engine comfortably absorbs the heavy, complex, machine-generated queries this architecture produces at interactive speed.

Both planning and query building draw on the semantic layer- the curated, machine-readable model of AppsFlyer’s business: data product schemas, changelogs, exact metric calculations, table relationships, and column value semantics. It’s deliberately the first stop in orchestration, so “revenue by media source” means the same thing whether it came from a scheduled report or an interactive question.

The trajectory layer: Embedded, stored, and searchable in AlloyDB

Our most powerful agents are deep research agents: given an open question, they autonomously explore AppsFlyer’s data in whatever direction the evidence leads — hypothesize, query, revise. A single run can issue 100–200 BigQuery queries before converging on an insight. An answer produced that way is only as trustworthy as your ability to inspect how it was produced.

So the platform records the entire trajectory of every run — reasoning steps, A2A tasks, ChaseSQL candidates, executed queries — and does more than log it: every step, together with its evaluation results, is embedded and stored in a pgvector database on AlloyDB, so “what the agent did” and “how well it did it” are queryable as one corpus.

-- Trajectory store :
CREATE TABLE trajectory_steps (
    run_id      UUID NOT NULL,
    step_index  INT  NOT NULL,
    step_type   TEXT NOT NULL,   -- reasoning | a2a_task | chasesql | bq_query | artifact
    payload     JSONB NOT NULL,  -- full step content: thought, task, SQL, result meta
    evaluation  JSONB,           -- per-step scores, judge verdicts, flags
    embedding   vector(768),
    PRIMARY KEY (run_id, step_index)
);
CREATE INDEX ON trajectory_steps USING hnsw (embedding vector_cosine_ops);

AlloyDB is what makes this layer viable at platform scale. A single deep-research run generates a burst of embeddings, and the workload is inherently hybrid: high-throughput writes of new execution steps happening concurrently with vector searches from the explorer. Standard PostgreSQL hits its limits quickly here; AlloyDB’s optimized pgvector performance absorbs both sides in real time, enabling insight discovery over live trajectories without back-pressure on the agents.

The embeddings turn the audit trail into semantic search over agent behavior:

  • The trajectory explorer in Agent Hub: a dedicated page where users ask natural-language questions about any run — “why did the research agent pivot to SKAN data at step 47?”, “which of the 180 queries support the final insight?” — answered by vector-searching that run’s embedded steps and synthesizing an answer with the exact steps as citations. Explainability becomes a conversation, not a log-diving exercise.
  • Semantic debugging: “find every run where an agent explored retention by media source and hit a schema mismatch” is a similarity query, not a grep through gigabytes of logs.
  • Experience reuse and evaluation fuel: the orchestrator can retrieve similar past trajectories as few-shot planning guidance, and every inspected failure becomes a benchmark case for the next release gate.

Verifiable autonomy: two hundred autonomous queries are acceptable precisely because we never lose the ability to audit — or search — how they were spent.

Business impact

The consolidation shows up in the numbers: 30,000 agents run in production on the platform every day, contributed by 1,300 accounts and serving tens of thousands of weekly active users across AppsFlyer. Onboarding a new agent dropped from ~4 weeks — a full deployment project in the silo era — to a few days: a card registration. And analytical questions that used to queue as tickets for data teams now resolve in minutes, with the deep research agents handling investigations that previously took an analyst days to weeks.

What the design buys us

  • Composability: blueprint agents, agent cards, and A2A-only communication mean every new agent — native or remote — multiplies what the platform can do instead of adding a silo.
  • Governance by construction: platform-owned runtime and configuration, a required governance extension on the wire, and self-fetching restrictions in ChaseSQL put enforcement where it can’t be bypassed.
  • Business-native intelligence: grounding every plan and query in the semantic layer is the difference between an orchestrator that sounds right and one that is right.
  • Verifiable autonomy: an embedded, searchable trajectory layer means autonomy and auditability scale together — not against each other.

Building an internal agent platform of your own?

Start with the layers teams most often skip: the semantic layer, centralized guardrails, and the trajectory layer. Everything else gets dramatically easier once intent is grounded, access is sealed, and behavior is inspectable. To avoid building from scratch, these repositories and resources reflect the architectural approaches described in this article:

5 Likes