EduPulse AI — Autonomous Student Success Agent

Inspiration

Students often drop out long before anyone notices. By the time an instructor spots a struggling student, the early warning signs — declining engagement, scattered absences, a missing assignment here and there — have already compounded into something hard to reverse. And the students themselves rarely know how to ask for help in a form an instructor can actually act on: they show up with a blurry screenshot of a failing grade, a chaotic PDF syllabus, a voice note at midnight saying "I don't know where to start."

I wanted to build an agent that meets both sides where they are. For the student: turn the messy multimodal evidence of their struggle into a concrete plan. For the instructor: do the noticing for them — wake up, scan the data, decide who needs help, apply the right intervention for each case, and then measure whether it worked so the next decisions improve on the last ones. And for everyone who has to trust the system: trace and evaluate every single Gemini call, so the agent's reasoning is observable, not folklore.

What it does

EduPulse AI has two entry points sharing one Gemini-driven core.

👩‍🎓 For students — /student

An empathetic onboarding wizard:

  1. Name → instant onboarding (step 2, not step 5) so every subsequent Gemini call is attributable to a real userId.
  2. Multimodal struggle extraction. Drop in a screenshot of your grade page, a PDF syllabus, or just paste text. Gemini 3 Flash reads it multimodally and pulls out the actual struggles — subjects, deadlines, themes.
  3. Personalized plan generation. Gemini reasons over the extracted struggles + the student's tasks and writes a concrete, tiered plan.
  4. AI Telemetry panel. The plan page surfaces every LLM call the agent made on the student's behalf — model, latency, token usage, evaluation scores — so the student (and any reviewer) can see exactly how the plan was reached.

👨‍🏫 For instructors — /instructor

A fully autonomous agent that runs a complete sense → reason → consult precedent → plan → act → measure → report loop in a single click.

  1. 🔍 SENSE — scans the MongoDB student cohort for at-risk candidates (engagement, grade, attendance, recency thresholds).
  2. 🧠 REASON — Gemini 3 Flash applies a graduated severity rubric and classifies each student low | medium | high | critical with specific risk factors and a priority score.
  3. 🧭 CONSULT PRECEDENT — for every high/critical student the agent autonomously calls a vector-search tool that runs $vectorSearch over Gemini-embedded past intervention outcomes in MongoDB Atlas. Gemini reads "this kind of profile improved when we scheduled a meeting; this kind didn't respond to email-only" and copies the patterns that historically worked.
  4. 📋 PLAN + ⚡ ACT — produces a tiered intervention per student (alerts for awareness, emails for medium+, 1:1 meetings only for high/critical) and writes every record to MongoDB through the partner MCP server.
  5. 📊 MEASURE — re-measures every intervention against its snapshotted baseline metrics and writes outcome rows. The resulting effectiveness percentage feeds back into the next cycle's precedent index.

Every decision Gemini makes is visible as a real [ADK] tool_call event in the live trace UI, and every database operation is a real [MCP] tools/call against the MongoDB MCP server.

🔭 For everyone — observability you can trust

Every Gemini call across both flows is wrapped in an OpenTelemetry LLM span and exported to Arize Phoenix. A second Gemini call runs as an LLM-as-judge, scoring extraction quality and plan quality on multiple dimensions; those scores are attached as eval.*.score / .label / .explanation attributes on the parent span. Judges and reviewers can audit the agent's reasoning end to end — and a mirrored copy lives in MongoDB so the in-app AI Telemetry panel works without Phoenix access.

Why this matters

Education systems generate massive amounts of student signal but lack the action layer that translates signal into intervention fast enough to change outcomes. EduPulse AI is that action layer — on both sides of the desk. Students get an AI that converts their own messy evidence into a plan; instructors get an agent that scans the cohort and rescues at-risk cases before they fail.

The same architecture — agent + partner MCP + vector-search precedent loop + traced/evaluated Gemini — generalizes to any domain where a small set of operators need to be amplified across a much larger population, and where the operators have to trust the agent's reasoning enough to delegate to it.

Key features

🤖 Real agentic orchestration via Google ADK (instructor flow)

  • Built on the Google Agent Development Kit (@google/adk) TypeScript SDK with LlmAgent + InMemoryRunner
  • Three custom FunctionTools: scan_at_risk_students, find_similar_past_cases, intervene_for_students
  • Gemini 3 Flash decides which tool to call, when to batch payloads, and when to stop — there is zero if (riskLevel === 'high') logic in the agent code path

📸 Multimodal Gemini extraction (student flow)

  • Single endpoint accepts multipart/form-data (image / PDF / voice note) or JSON (raw text) — same downstream pipeline
  • Gemini 3 Flash processes audio natively: a student can say "I'm overwhelmed, I have a midterm Friday and I'm behind in calc" and the agent transcribes + extracts struggles in one pass
  • Gemini 3 Flash extracts struggles, subjects, and deadlines from whatever the student drops in
  • Output is shape-guaranteed via responseMimeType: 'application/json'
    • responseSchema
  • Onboarding happens at wizard step 2, not step 5, so every extraction is linkable to a real userId from the very first call

🔌 Real MongoDB MCP server integration (24 tools)

  • The official mongodb-mcp-server is spawned as a stdio child process at startup
  • The agent discovers all 24 tools via tools/list and routes every read and write through MCP tools/call (find, insert-many, update-many, aggregate, …)
  • No direct MongoDB driver imports anywhere in the agent module — proven by the trace, which shows interleaved [ADK] and [MCP] lines per cycle

🧭 MongoDB Atlas Vector Search for precedent

  • Past outcomes are embedded via Gemini Embedding (gemini-embedding-001, 768 dims via Matryoshka truncation)
  • Atlas Vector Search index (outcome_embedding_idx, cosine similarity) created programmatically via the Node driver
  • find_similar_past_cases issues a $vectorSearch aggregation through MCP and returns top-5 historically similar cases with their actual outcomes
  • The auto-rebuild flow means every cycle's outcomes become the next cycle's precedent — the agent literally learns from itself

🔭 Arize Phoenix observability + LLM-as-judge evaluation

  • Every Gemini call is wrapped in traceLlm() → one OpenInference LLM span exported to Phoenix Cloud
  • Token usage is auto-extracted from the response and attached
  • A second Gemini call runs after every primary call as an LLM judge: evaluateExtraction scores multimodal accuracy, evaluatePlan scores on three dimensions
  • Eval scores are attached to the parent span as eval.<name>.{score, label, explanation} — Phoenix renders them natively
  • BatchSpanProcessor is tuned to scheduledDelayMillis: 500 so demos don't wait for the default 5-second flush
  • All @opentelemetry/* packages are kept in serverExternalPackages — bundling them silently breaks global tracer registration

📺 In-app AI Telemetry panel

  • Every Gemini call also writes to a llm_traces MongoDB collection (best-effort — failures swallowed; observability must never crash a student's plan)
  • /student/plan reads from this collection and renders the same spans, latencies, token counts, and eval scores in-app
  • Judges and reviewers who can't access our Phoenix tenant still see live LLM observability data without leaving the product

📈 Real outcome measurement (no synthetic placeholders)

  • Each intervention snapshots baselineMetrics at creation time
  • A post-intervention measurement phase reads current student state via MCP and computes the diff against the real baseline
  • overallImproved requires at least 2 of 3 metrics to improve AND no metric to regress by more than 10 points — strict, honest
  • Dashboard's Intervention Effectiveness tile shows the real percentage derived from these comparisons (or "—" with "No outcomes tracked yet" if the agent hasn't run yet)

⚙️ Graduated intervention triage

Risk level Alert Email Meeting
low
medium
high / critical

🖥️ Live trace UI via Server-Sent Events

  • The instructor homepage streams every tool_call / tool_result event from the agent in real time so judges can watch Gemini reason
  • One click runs the entire loop — agent then measurement — without a second action from the user

📊 Interactive analytics dashboard

  • Real-time tiles (total students, at-risk, interventions, effectiveness)
  • Risk-distribution pie chart and 7-day activity timeline (Recharts)
  • Every tile opens a modal that fetches and displays the underlying MongoDB documents — judges can verify the data behind the numbers

💾 Production data integration path

In production, EduPulse AI would consume student data from existing LMS systems (CYPHER Learning, Canvas, Moodle, Blackboard) via their APIs. For this hackathon I seeded 50 realistic mock students; the schema is designed to accept LMS data with minimal change.

How I built it

Architecture

Frontend — Next.js 16 (App Router, React 19, React Compiler in production builds).

Two entry points:

  • /student (wizard) — Gemini 3 Flash multimodal extraction → plan generation
  • /instructor (ADK loop) — Google Agent Development Kit + Gemini 3 Flash + 3 FunctionTools

Observability layer (both flows) — every Gemini call is wrapped in traceLlm(), producing one OpenInference LLM span plus an LLM-as-judge eval pass. Spans export in parallel to:

  • Arize Phoenix Cloud — full observability for reviewers with access
  • MongoDB llm_traces — in-app AI Telemetry panel for everyone else

Data layer (instructor flow):

  • MongoDB MCP Servermongodb-mcp-server spawned as a stdio child process; 24 tools discovered at runtime
  • MongoDB Atlas — 8 collections + Atlas Vector Search index (outcome_embedding_idx, cosine, 768-dim) for find_similar_past_cases

Development process

I started with a hand-rolled StudentSuccessAgent class to validate the instructor loop end-to-end. Once it worked, I migrated the orchestration onto Google's Agent Development Kit — which is what the hackathon explicitly asked for and what turns "automation" into a real agent.

The first ADK port exposed all 24 MongoDB MCP tools to Gemini directly via MCPToolset. It failed in a fascinating way: Gemini mangled MongoDB filter operators in function-call JSON ($orv_or, grades.scorev_grades_score). The fix was to wrap the MCP calls in domain-specific FunctionTools with clean signatures, then let Gemini compose them. Real MCP traffic still runs underneath.

The MongoDB partner-track upgrade came next: I added the third tool find_similar_past_cases backed by Atlas Vector Search. The agent embeds past intervention outcomes with gemini-embedding-001 (768 dims), stores them with an index on embedding (cosine) plus filter dimensions on riskLevel and outcome.overallImproved, and issues $vectorSearch queries through MCP. Gemini now consults precedent before deciding intervention plans.

Then came honest measurement: each intervention snapshots baselineMetrics, a measurement endpoint re-reads current student state and writes outcomes with a strict overallImproved rule (≥2 metrics up, no metric down by >10). After measurement, the embeddings rebuild automatically so the next cycle's precedent search benefits from the latest results.

The last layer was the student-facing flow and observability. I realized the instructor agent had no analog for the student — the person actually living the struggle. So I built a multimodal extraction pipeline (drop a screenshot or PDF, Gemini reads it), fed the extracted struggles into a plan-generation call, and wrapped both behind an empathetic onboarding wizard. Then, because trusting Gemini to plan a student's recovery is a high bar, I wired every single Gemini call — student-side and instructor-side — through traceLlm() to Arize Phoenix, added a second-pass Gemini LLM-as-judge that scores extraction and plan quality, and mirrored every span to a llm_traces MongoDB collection so the in-app AI Telemetry panel works for anyone without Phoenix access.

Challenges I ran into

  1. Gemini function-calling mangled MongoDB operators. Exposing raw MCP tools to Gemini failed because $or, $lt, and dotted field paths don't survive OpenAPI-style function-call JSON cleanly. Solved by wrapping the MCP calls in domain-specific FunctionTools — Gemini decides intent, our tool handler builds the MongoDB syntax.

  2. mongodb-mcp-server transitive deps and Next.js standalone builds. The MCP server runs as a child process, so Next's standalone tracer doesn't see its imports. The Docker image missed @mongodb-js/devtools-proxy-support at runtime and died on first request. Fixed with a four-stage Dockerfile: a separate prod-deps stage runs npm ci --omit=dev and copies the complete node_modules into the runtime image.

  3. The find tool's untrusted-data envelope. The MCP server wraps query results in <untrusted-user-data-UUID>…</…> tags, with surrounding warning prose that also mentions the tag names literally. A naive regex matches the inline mention first. The parser iterates every match in the block and picks the first one that parses as a JSON array.

  4. Canonical EJSON dates. The MCP server validates inputs with EJSON.deserialize(value, { relaxed: false }) — dates must be { $date: { $numberLong: "<ms>" } }, not the relaxed { $date: "ISO string" } form. Used the wrong form initially and the dashboard's activity timeline silently went empty until I wrote an ejsonDate() helper.

  5. Trivial 100% effectiveness. First version of overallImproved was a simple OR (any metric up = improved). One bumped engagement metric masked grade regressions, so everyone counted as improved and the dashboard showed 100%. Replaced with "at least 2 of 3 metrics up, AND no metric regresses by more than 10 points".

  6. Atlas Vector Search index creation is asynchronous. Calling createSearchIndex returns immediately but the index isn't queryable for ~1–2 minutes. Made the rebuild endpoint idempotent (swallows IndexAlreadyExists) and surfaced a clear status message in the UI.

  7. Phoenix Cloud auth is Authorization: Bearer <key>, not api_key: <key>. The self-hosted Phoenix docs use api_key:, which silently returns 401 against Phoenix Cloud — exports just stop arriving with no error. Caught it only after turning on diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO) so OTel's normally-swallowed export failures became visible. Now wired correctly with Authorization: Bearer ….

  8. OpenTelemetry singletons vs Next.js bundling. Letting Turbopack bundle @opentelemetry/* broke global tracer registration — the SDK relies on module-level singletons that don't survive Next's inlining. Fix was to keep every @opentelemetry/* package in serverExternalPackages in next.config.ts. Related: the boot hook must live in a separate instrumentation.node.ts because sdk-trace-node can't load in the Edge runtime.

  9. LLM-as-judge span attribution. Calling the judge as a traceLlm-wrapped sibling produced two unconnected spans in Phoenix. The fix: call the judge inside the primary call's wrapped function, BEFORE the primary span ends, so annotateSpanWithEval attaches eval.* attributes to the primary span. Now Phoenix shows the eval scores natively next to the call they evaluate, and the judge appears as a child span.

  10. Wizard onboarding step. First version onboarded the student at the very end (step 5), which meant the first multimodal extraction call had no userId and couldn't be linked back to the student in telemetry. Moved onboarding to step 2 (right after name) so every extraction call from then on is attributable.

Accomplishments I'm proud of

  • Real ADK + Gemini 3 agent. Every tool selection is Gemini's decision, visible in the trace. No hidden if/else orchestrating the agent.
  • Real partner MCP integration. Not a stub — the agent literally spawns the official mongodb-mcp-server and routes every database operation through tools/call.
  • MongoDB Atlas Vector Search closing the learning loop. The agent embeds its own outcomes and consults them next cycle. This is the difference between "rules with Gemini in the middle" and "an agent that gets better over time".
  • Multimodal student onboarding. A screenshot, a PDF, or a paragraph of text — Gemini turns any of them into structured struggles, then into a concrete plan.
  • Full Gemini observability. Every single Gemini call across both flows produces a Phoenix span with token usage, latency, and LLM-as-judge eval scores attached.
  • Dual telemetry surface. Same data lives in Phoenix Cloud AND in-app — judges without Phoenix access see exactly what we see.
  • Honest effectiveness measurement. Real baseline → current comparisons with a strict improvement rule. No synthetic placeholder.
  • Live SSE trace UI. Judges and instructors see every tool decision as it happens.
  • Production deployment with proper Secret Manager + four-stage Docker build.
  • Solo build end-to-end in the hackathon window.
  • Truly multimodal intake. Screenshot, PDF, text, or voice — Gemini 3 Flash handles all four through one extraction endpoint.

What I learned

  • ADK function-calling is the right abstraction, but the boundary between Gemini and the database has to be designed carefully — raw operators don't survive function-call JSON.
  • MCP is real and useful when you treat partner servers as external processes, not as types you import.
  • Vector search precedent is a force multiplier for agents. Reasoning from past outcomes beats reasoning from rules alone.
  • Effectiveness rules need to be strict. OR-of-anything produces meaningless 100% rates; a 2-of-3 rule with a regression floor produces numbers you can defend.
  • Async index creation needs explicit UX. Don't silently rely on "it'll be ready eventually".
  • Observability is a product feature, not infra. Mirroring Phoenix traces into MongoDB so the in-app panel works without Phoenix access turned a "trust us" pitch into a "look for yourself" pitch.
  • LLM-as-judge belongs inside the parent span, not beside it. Otherwise reviewers can't tell which judgment evaluates which call.
  • OpenTelemetry doesn't survive aggressive bundlers. Keep the SDK packages external and the boot hook split for Node-only loading.
  • Cloud Run + Docker + child processes require a separate prod-deps stage when the child can't be statically traced.

What's next for EduPulse AI

Near-term

  • LMS connectors. CYPHER Learning, Canvas, Moodle, Blackboard. Replace the seeded cohort with live student feeds.
  • Real action delivery. SendGrid for email, Google Calendar for meeting invites, Slack/Teams for instructor notifications.
  • Scheduled cycles. Cloud Scheduler hits /api/agent/stream daily so the agent runs autonomously instead of on a button click.

Longer-term

  • A/B test intervention strategies — let the agent try variations and use the outcome loop to converge on what works best per student archetype.
  • Use eval scores as a training signal. The LLM-as-judge already scores every plan; feeding low-score plans back into prompt refinement (or fine-tuning) closes a second learning loop on top of the vector-search precedent one.
  • Multi-institution deployment with role-based access and cohort isolation.
  • Mobile app for instructors with push notifications when a critical case is identified.
  • Student-facing portal expansion — the plan view already surfaces the agent's reasoning; next is letting the student push back ("this plan won't work because…") and re-plan in place.

Try it out

  1. Land on / and pick a role
  2. As a student: drop a screenshot, a PDF of your syllabus, or record a voice note about what you're struggling with — Gemini reads or listens, extracts your struggles, and builds a plan. Then open the AI Telemetry panel to see every LLM call that produced it
  3. As an instructor: click 🧪 Reset Demo Data, then 🚀 Run Agent Cycle — watch the live trace populate as Gemini scans, consults precedent via Atlas Vector Search, and intervenes
  4. Click 📊 Dashboard for analytics; click any stat tile to inspect the underlying MongoDB documents
  5. Run the agent again — fewer at-risk students this time, because the previous interventions worked

Built for the Google Cloud Rapid Agent Hackathon · MongoDB Partner Track Developer: Mohamad El Masri · License: MIT

Built With

  • bson
  • docker
  • ejson
  • gemini
  • git
  • github
  • google-cloud-run
  • google-gemini-api
  • google-generative-ai
  • google-secret-manager
  • google/adk
  • model-context-protocol
  • mongodb
  • mongodb-atlas
  • mongodb-atlas-vector-search
  • mongodb-mcp-server
  • next
  • next.js
  • node.js
  • react
  • recharts
  • tailwind
  • tailwind-css
  • typescript
Share this project:

Updates