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:
- Name → instant onboarding (step 2, not step 5) so every
subsequent Gemini call is attributable to a real
userId. - 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.
- Personalized plan generation. Gemini reasons over the extracted struggles + the student's tasks and writes a concrete, tiered plan.
- 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.
- 🔍 SENSE — scans the MongoDB student cohort for at-risk candidates (engagement, grade, attendance, recency thresholds).
- 🧠 REASON — Gemini 3 Flash applies a graduated severity
rubric and classifies each student
low | medium | high | criticalwith specific risk factors and a priority score. - 🧭 CONSULT PRECEDENT — for every high/critical student the
agent autonomously calls a vector-search tool that runs
$vectorSearchover 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. - 📋 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.
- 📊 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 withLlmAgent+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
userIdfrom the very first call
🔌 Real MongoDB MCP server integration (24 tools)
- The official
mongodb-mcp-serveris spawned as a stdio child process at startup - The agent discovers all 24 tools via
tools/listand routes every read and write through MCPtools/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_casesissues a$vectorSearchaggregation 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:
evaluateExtractionscores multimodal accuracy,evaluatePlanscores on three dimensions - Eval scores are attached to the parent span as
eval.<name>.{score, label, explanation}— Phoenix renders them natively BatchSpanProcessoris tuned toscheduledDelayMillis: 500so demos don't wait for the default 5-second flush- All
@opentelemetry/*packages are kept inserverExternalPackages— bundling them silently breaks global tracer registration
📺 In-app AI Telemetry panel
- Every Gemini call also writes to a
llm_tracesMongoDB collection (best-effort — failures swallowed; observability must never crash a student's plan) /student/planreads 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
baselineMetricsat creation time - A post-intervention measurement phase reads current student state via MCP and computes the diff against the real baseline
overallImprovedrequires 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 | Meeting | |
|---|---|---|---|
low |
✅ | — | — |
medium |
✅ | ✅ | — |
high / critical |
✅ | ✅ | ✅ |
🖥️ Live trace UI via Server-Sent Events
- The instructor homepage streams every
tool_call/tool_resultevent 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 Server —
mongodb-mcp-serverspawned as a stdio child process; 24 tools discovered at runtime - MongoDB Atlas — 8 collections + Atlas Vector Search index (
outcome_embedding_idx, cosine, 768-dim) forfind_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 ($or →
v_or, grades.score → v_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
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-specificFunctionTools — Gemini decides intent, our tool handler builds the MongoDB syntax.mongodb-mcp-servertransitive 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-supportat runtime and died on first request. Fixed with a four-stage Dockerfile: a separateprod-depsstage runsnpm ci --omit=devand copies the completenode_modulesinto the runtime image.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.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 anejsonDate()helper.Trivial 100% effectiveness. First version of
overallImprovedwas 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".Atlas Vector Search index creation is asynchronous. Calling
createSearchIndexreturns immediately but the index isn't queryable for ~1–2 minutes. Made the rebuild endpoint idempotent (swallowsIndexAlreadyExists) and surfaced a clear status message in the UI.Phoenix Cloud auth is
Authorization: Bearer <key>, notapi_key: <key>. The self-hosted Phoenix docs useapi_key:, which silently returns 401 against Phoenix Cloud — exports just stop arriving with no error. Caught it only after turning ondiag.setLogger(new DiagConsoleLogger(), DiagLogLevel.INFO)so OTel's normally-swallowed export failures became visible. Now wired correctly withAuthorization: Bearer ….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 inserverExternalPackagesinnext.config.ts. Related: the boot hook must live in a separateinstrumentation.node.tsbecausesdk-trace-nodecan't load in the Edge runtime.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, soannotateSpanWithEvalattacheseval.*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.Wizard onboarding step. First version onboarded the student at the very end (step 5), which meant the first multimodal extraction call had no
userIdand 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/elseorchestrating the agent. - Real partner MCP integration. Not a stub — the agent literally
spawns the official
mongodb-mcp-serverand routes every database operation throughtools/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/streamdaily 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
- Land on
/and pick a role - 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
- 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
- Click 📊 Dashboard for analytics; click any stat tile to inspect the underlying MongoDB documents
- 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
Log in or sign up for Devpost to join the conversation.