The build behind NetPulse AI

Architecture, data, and what it took to ship — from the four-agent SequentialAgent down to the indexes on the bundled SQLite store.

phase Top 100 · refinement due 2026-04-30 stack ADK · Vertex · MCP Toolbox · SQLite code github / hackathon-telecom-ops
/ 01 — About

About NetPulse AI

NetPulse AI is a multi-agent telecom operations assistant built for the APAC GenAI Academy 2026 hackathon. A natural-language complaint goes in; a structured incident ticket comes out — complete with the related network events the operator should know about, the CDR findings that back up the customer’s account of what happened, and a recommended NOC action plan.

/ 02 — Architecture

Architecture in detail

The core ADK package telecom_ops exposes a SequentialAgent that runs four LlmAgent sub-agents in order: classifier, network investigator, CDR analyzer, response formatter. Each one reads the shared session state, queries its data store through the MCP toolbox, and enriches the state for the next. The fourth writes a structured ticket to the bundled SQLite store and the run ends.

two substrates, one design

The original hackathon architecture ran on BigQuery (network_events) plus AlloyDB (call_records and incident_tickets) — managed, durable, and partition-aware. The deployment you’re reading about here is the simplified low-cost demo mode: the same three tables collapsed onto a single bundled SQLite file, served through the same MCP Toolbox tools. The agent code is identical between the two; only toolbox-service/tools.yaml’s sources: block flips between kind: sqlite, kind: alloydb-postgres, and kind: bigquery.

NetPulse AI architecture diagram
User → UI → SequentialAgent (4 LlmAgents on Gemini) → MCP Toolbox → SQLite

Why a SequentialAgent

The four steps have strict data dependencies — the network investigator can’t run until the classifier has tagged a region; the CDR analyzer joins on the time window the network investigator returned; the formatter needs all three to write a coherent ticket. A SequentialAgent models this as code rather than encoding it into a prompt, so the dependency stays correct under prompt churn.

Why MCP Toolbox in front of the data store

The toolbox-as-intermediary pattern gives one place to evolve tool definitions without redeploying the agent service. Swap the toolbox sources: block from kind: sqlite to a managed backend (AlloyDB, Cloud SQL, BigQuery) and the agent code does not change at all — only the toolbox YAML moves.

Why SQLite bundled in the container

The data substrate is a single ~10 MB SQLite file baked into the container image. Two reasons that mattered for this iteration: zero idle cost (no AlloyDB cluster or BigQuery dataset to keep alive), and a demo that boots clean on a fresh project with no infra setup — scripts/build_sqlite.py regenerates the file from the seed CSVs in seconds. Trade-offs: no cross-instance ticket durability on Cloud Run scale-to-zero, and no NL2SQL story (AlloyDB AI’s execute_nl_query is AlloyDB-specific). The two parameterized CDR tools cover the demo’s prompt surface; durable ticket persistence is a managed-backend upgrade away.

/ 03 — Tech stack

Tech stack on Google Cloud

The complete list of moving parts. Everything is Google Cloud (the hackathon track requires it); the LLM-side work is Vertex AI Gemini.

ORCHESTRATION
Google ADK · SequentialAgent
Coordinates four LlmAgents in order, threading session state forward through output_key handoffs.
MODELS
Vertex AI · Gemini
All four agents run on gemini-3.1-flash-lite-preview at the global endpoint, with a 4-attempt model ladder failing over via gemini-3-flash-preview to gemini-2.5-flash under quota pressure.
NETWORK DATA
SQLite · network_events
network_events table inside the bundled SQLite file — 50,000 events across 10 metros over a 6-month rolling window. Indexed on (region, severity, started_at) for the time-windowed scans the network investigator runs.
CDR DATA
SQLite · call_records
call_records served by two parameterized SQL tools: query_cdr_summary (call_type × call_status breakdown) and query_cdr_worst_towers (per-tower failure ranking). Both execute in <50ms.
TICKET SINK
SQLite · incident_tickets
incident_tickets — append-only writes via the native ADK tool save_incident_ticket. AUTOINCREMENT picks up from the seed’s MAX(ticket_id)+1 so agent-written tickets don’t collide with seeds.
TOOL TRANSPORT
MCP Toolbox
Five parameterized SQL tools (3 in telecom_network_toolset, 2 in cdr_toolset) served by the genai-toolbox v0.23.0 binary against the same bundled SQLite file. Agents reach the data via the toolbox; the toolbox reaches the file via kind: sqlite-sql.
FRONT END
Flask + SSE
netpulse-ui wraps the same root_agent in a hero landing + workspace timeline. Each request runs its own asyncio loop in a worker thread; events stream out incrementally via Server-Sent Events.
DEPLOY
Cloud Run · 2 services
netpulse-ui serves the chat surface; network-toolbox hosts the MCP toolbox. Both built from a Dockerfile in the project root, deployed from main.
/ 04 — Data schema

Data schema three surfaces

Three data surfaces feed the run. Each viewer page describes its schema and filter dimensions in detail.

/ 05 — Bring your own data

Bring your own data in three steps

NetPulse AI ships with seed data for ten Indonesian metros. Adapting to a different telecom dataset is a three-step exercise:

  1. Replace the seed CSVs in docs/seed-data/ with your own network_events.csv, call_records.csv, and incident_tickets.csv. Keep the column shapes intact — see network events schema and call records schema for column-by-column descriptions.
  2. Re-run python scripts/build_sqlite.py --recreate to wipe the existing data/netpulse.sqlite and rebuild from your CSVs. The script materializes all three tables in one go and is idempotent.
  3. Adjust the region whitelist in netpulse-ui/data_queries.py (ALLOWED_REGIONS) and the prompt examples in telecom_ops/prompts.py if your cities diverge from the Indonesian-metro defaults. The agents will pick up the new vocabulary on next deploy.

The agents themselves are dataset-agnostic — what changes is the data, the region whitelist, and the parameterized-SQL bodies in toolbox-service/tools.yaml if your schema diverges.

/ 06 — Phase history

Phase history over time

NetPulse AI was built through a series of timeboxed phases. Each phase landed in a single PR (in most cases) and was visually verified end-to-end before the next began.

/ 07 — Roadmap

Roadmap next

The hackathon scope is a refined prototype, not a product. These are the next directions if the project continues past 2026-04-30.

NEXT
Custom-schema BYO data
Today, swapping in your own data needs CSV reshaping. Next: a schema-mapping config so any telecom-shaped feed (CDR + events + ticket sink) plugs in without touching code.
SOON
Managed-backend swap path
Today the substrate is SQLite bundled in the container; tomorrow the same MCP Toolbox config flips to AlloyDB, Cloud SQL, or BigQuery without agent-code changes. Adds durable cross-instance ticket persistence and reopens the NL2SQL story.
MAYBE
React rewrite of the workspace
The current workspace is server-rendered Flask + a single inline JS handler. A SPA would unlock streaming-state inspection (per-agent JSON view, replay, diffing).