How M3ATRON Decides What to Teach Next
How our AI tutor turns course PDFs into grounded answers and changes its teaching strategy when a student stays stuck.
Applied AI & Full-Stack Engineer

A student asks what a database index is. A minute later, they say they still don’t understand why a query can be slow even when an index exists. Both messages are about indexes, but they call for different teaching moves. The first needs a definition grounded in the course notes. The second might need a step back into how a database finds and reads rows.
That distinction shaped M3ATRON, the AI tutoring system my team built for our final-year project at the University of Management and Technology. I owned the tutoring engine, authentication, and chat APIs. Mujtaba Raza owned the document and retrieval pipeline, and Murad Ahmad owned the Next.js interface. Matte-ur-Rehman was also part of the project team, supervised by Syed Hasan.
From an instructor’s PDF to a student’s answer
The application has a Next.js frontend and two Python services. Module A handles tutoring, authentication, and chat sessions. Module B processes course materials and retrieves relevant passages. LangGraph decides which tutoring path to take, and FastAPI streams the answer to the browser using server-sent events.
Here’s how those pieces connect when an instructor uploads a PDF and a student asks a question about it.
- The instructor uploads a PDF to a course. Module B stores it in Supabase Storage and marks the material as pending. A background task extracts the text, splits it into chunks, creates embeddings, and writes them to the configured vector store. The material status changes to indexed or failed, so the upload and indexing have separate outcomes.
- The student asks a question in a chat session. Module A verifies access to the session and uses the course attached to it for retrieval. If the request supplies a different course ID, the session’s course takes precedence.
- The router picks a teaching strategy. A factual question such as “What is a database index?” goes to Teacher. That path searches the course material, requesting the top three chunks by default. The returned passages include their source and page, which the tutor can use when forming the answer.
- The browser receives the response as it’s generated. The API sends the responding persona in a metadata event, streams the answer in token events, then sends citations and a completion event. That gives the interface separate data for the answer and the material behind it.
When the student is still stuck
The graph chooses one path per turn. Teacher retrieves relevant course material and explains it. Devil’s Advocate challenges a premise when the router classifies the message as an assumption and the instructor has enabled that behavior. Instructors can set its style and intensity for the course. This path doesn’t call document retrieval.
Backtrace handles repeated confusion. The router increments a session counter when it classifies a message as confused. When the count reaches two, Backtrace changes the search to look for prerequisites behind the current question. It returns those passages to the Teacher persona. From the student’s point of view, the same tutor is responding, but the material it draws on has shifted from the immediate question to the concepts underneath it.
For example, if two follow-ups about index performance are classified as confusion, the search starts looking for foundational concepts behind the latest question. Depending on the uploaded material, that could surface pages on table scans or how a database looks up rows. The useful change is in what the tutor retrieves before it starts writing.
What happens if the student closes the tab?
We used to save the student’s question after the answer finished streaming. Closing the tab midway could leave the conversation with no record of the question. The chat API now saves the question before calling the model. It also attempts to save any answer text it has already received when the stream ends early.
Long conversations posed a separate problem. The model receives recent turns and a running summary of older context. The code drops the oldest messages first when it needs room in the context window. After a reply completes, the service can update the summary in the background, keeping that extra model call off the student’s response path.
What I’d measure next
I’d start with short, labeled tutoring conversations that exercise each route. Did a factual question retrieve a relevant page? Did a challenge address the student’s actual premise? When Backtrace ran, did it bring back a useful prerequisite? I’d check those separately from whether the answer sounded fluent.
Those checks follow the decisions the application actually makes. They’d tell us where to work next: the intent classifier, the course retrieval, or the answer prompt.