# Project export: EigenCoach

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: What if we can generate an agentic AI tutor for any exam in the world?
- Devpost: https://devpost.com/software/eigencoach
- GitHub: https://github.com/sawansri/calhacks-eigen-coach-backend
- Demo: https://github.com/Storce/calhacks-eigen-coach-frontend
- Video: https://www.youtube.com/embed/w2PF-A-kz0k?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Joe Wang (31 commits), Sawan Srivastava (13 commits)

## Devpost submission (written by the team)

### Inspiration

Joe, one of our members, used to teach in public schools in Brazil, where he grew up. He witnessed first-hand the biggest struggle students there face: exam prep for the national examination. Every year, over 7 million students prepare for the high-stakes Brazilian ENEM exam each year. But public school students face a massive resource gap: the lack of access to the expensive private tutors and quality prep materials. But it's not just about ENEM. The truth is, big platforms like Khan Academy or Udemy don't have classes for smaller exams from more marginalized corners of the world. So what if we can generate an AI tutor for ANY exam in the world?

### What it does

Eigen Coach is a personal, AI-powered tutor that manages a student's entire exam prep journey. It Plans: A student signs up, and the Initializer agent creates a personalized, time-aware study schedule based on their exam date. The agent learns about the exam and update knowledge base using the material student submits. It Quizzes: The Questioner agent selects a targeted question from our MySQL database based on the day's topics and the student's current skill level. It Tutors: The Chatter agent engages the student in a Socratic conversation. It's designed to never give the direct answer, but instead guides the student to discover the solution themselves, just like a real tutor would. It Tracks: The Finalizer agent analyzes the entire conversation, scores the student's performance on a 0-100 scale, and automatically updates their skill profile in the database. This creates a closed loop where the tutor gets smarter about the student's needs after every single session.

### How we built it

We built Eigen Coach on a robust, asynchronous Python backend using FastAPI. The core of our project is a Clean 4-Agent System that manages the entire student lifecycle without a complex central orchestrator. We use Claude-Sdk as the foundation of our multi-agent model. All data—from the question bank to student memory and skill levels—is stored in a unified MySQL database. The agents interact with the database safely and efficiently through a single MCP server that exposes all necessary tools (e.g., get_question_by_topic, update_skill_level). We used Pydantic to ensure all our models are type-safe and Docker to containerize our MySQL instance for easy setup and development.

### Challenges we ran into

Prompting the Socratic Tutor: Our biggest challenge was engineering the Chatter agent's prompts. It's incredibly difficult to make an LLM guide a student to an answer (the Socratic method) instead of just providing it. This required dozens of iterations to ensure it was helpful without becoming a cheat-sheet. Stateful Conversations: Managing the chat state for the Chatter agent was complex. We had to ensure each conversation was cleanly scoped to a single question and that the agent had the correct session context (the question, the answer, and the session_id) for every turn. Agent Coordination: Designing the 4-agent system to work "cleanly" without a central orchestrator was tough. We had to be very disciplined about giving each agent a single responsibility and using the MySQL database as the central source of truth.

### Accomplishments we're proud of

The Socratic Chatter Agent: We are incredibly proud of the Socratic tutoring. It's the core of our "why"—it doesn't just give answers, it truly teaches. Seeing it successfully guide a user from "I don't know" to the correct answer is the magic of this project. The "Closed-Loop" Learning System: The most powerful feature is our full-circle adaptive loop. The Finalizer agent evaluates a student's chat, and its output (a new skill score) is immediately used by the Questioner agent to select the next question. The system adapts in real-time to the student's growth. The Unified Database Architecture: Committing to a single MySQL database for everything (questions, student data, memory, skills) was a great decision. It makes the entire system robust, scalable, and easy to query.

### What we learned

The Nuance of AI in Education: We learned that the true power of AI in education isn't just information retrieval, but guided discovery. Building a Socratic tutor is far more challenging, and ultimately more valuable, than building a simple Q&A bot. The Database as the "Brain": We learned to use a unified database as the central "brain" and state-holder for a multi-agent system. It's far more robust and scalable than passing complex JSON objects between API calls. Agent-Based Design: We gained deep experience in designing "clean" agents with single responsibilities. This separation of concerns (e.g., Questioner only picks questions, Finalizer only grades) makes the whole system more resilient.

### What's next

Implement a Reinforcement Learning Recommender: We will evolve the Questioner agent from a rule-based selector into a true AI strategist. By implementing a Deep Q-Network (DQN), the agent will learn the optimal policy for which question to ask next to maximize a student's long-term learning.

## README (from the GitHub repository)

# Eigen Coach Backend

AI-powered tutor for exam prep. Built for Brazilian public schools where students lack resources for private tutors and standardized test preparation materials.

## 🎯 Core Features

- **Time-Aware Study Planning**: Generates personalized study schedules based on exam dates and student availability
- **Question Bank**: MySQL-backed question database with topic tagging and difficulty scoring
- **Student Memory**: Tracks learning progress, skill levels, and personalized notes per student
- **Adaptive Tutoring**: Socratic method chatbot that guides students without giving away answers
- **Performance Tracking**: Evaluates conversations and updates skill levels automatically

## 🏗️ Architecture

### **Clean 4-Agent System** (No Orchestrator)

1. **Initializer** (`agents/initializer.py`)
   - Sets up student sessions and calendar entries
   - Creates default study plans

2. **Questioner** (`agents/questioner.py`)
   - Selects appropriate questions based on date, topics, and skill levels
   - Uses unified database MCP for question retrieval

3. **Chatter** (`agents/chatter.py`)
   - Streaming Socratic tutoring conversations
   - Automatically saves student learning notes via MCP
   - Never gives direct answers - guides discovery

4. **Finalizer** (`agents/finalizer.py`)
   - Analyzes conversation history
   - Evaluates student performance (0-100 scale)
   - Updates skill levels in database

### **Unified Database Layer**

All data now stored in **MySQL** with a single unified MCP server:

**`database/`** folder contains:
- `db.py` - MySQL connection pool manager
- `db_helpers.py` - CRUD operations (students, memory, calendar, skills)
- `db_mcp.py` - Unified MCP server with 6 tools:
  - **Question Bank**: `get_question_by_topic()`, `get_unique_topics()`
  - **Student Data**: `get_skill_level_pairs()`, `get_topics_by_date()`, `add_memory_entry()`, `update_skill_level()`
- `init.py` - Database initialization

**Database Tables:**
- `questions` - Question bank with topic tags and difficulty
- `students` - Student metadata (name, exam)
- `student_memory` - Learning notes and observations
- `calendar_entries` - Study session plans
- `skill_levels` - Topic proficiency scores (0-100)

## 📡 API Endpoints

All endpoints available at `http://localhost:8000`

### `GET /health`
Health check

### `POST /initializer`
Initialize a student session
```json
{
  "student_data": {
    "student_name": "Maria",
    "exam_name": "ENEM 2025",
    "memory": []
  },
  "date": "2025-01-15"
}
```

### `POST /questioner`
Get a question for the student
```json
{
  "student_data": { ... },
  "date": "2025-01-15"
}
```

### `POST /chatter`
Send message to tutoring chatbot
```json
{
  "student_data": { ... },
  "user_message": "I think the answer is...",
  "conversation_history": []
}
```

### `POST /finalizer`
Evaluate session and update skill levels
```json
{
  "student_data": { ... },
  "conversation_history": [
    {"role": "user", "content": "..."},
    {"role": "assistant", "content": "..."}
  ]
}
```

## 🚀 Quick Start

### 1. Install Dependencies
```bash
pip install -r requirements.txt
```

### 2. Start MySQL
```bash
# Using Docker
docker run -d --name calhacks-mysql \
  -e MYSQL_ROOT_PASSWORD=joe_is_very_cool \
  -p 8003:3306 \
  mysql:8
```

### 3. Run the Server
```bash
# Database initializes automatically on startup
python -m uvicorn main:app --reload
```

Server will display:
```
============================================================
Eigen Coach Backend Starting
============================================================

[Startup] Initializing database connection pool...
[Startup] ✓ Database initialized and ready
[Startup] ✓ API endpoints available

============================================================
Server Ready!
============================================================
```

## 🗂️ Project Structure

```
backend/
├── agents/                     # 4 clean agents
│   ├── chatter.py             # Tutoring chat
│   ├── finalizer.py           # Performance evaluation
│   ├── initializer.py         # Session setup
│   └── questioner.py          # Question selection
├── database/                   # Unified database layer
│   ├── db.py                  # MySQL connection pool
│   ├── db_helpers.py          # CRUD operations
│   ├── db_mcp.py              # Unified MCP server
│   └── init.py                # Initialization
├── migrations/
│   └── 001_create_memory_tables.sql
├── api.py                      # FastAPI endpoints
├── main.py                     # Entry point
└── requirements.txt
```

## 🔧 Configuration

### MySQL Connection
Edit `database/db.py`:
```python
host='localhost'
port=8003
user='root'
password='joe_is_very_cool'
database='calhacks'
```

### MCP Server
Configuration in `.mcp.json`:
```json
{
  "mcpServers": {
    "database": {
      "command": "python3",
      "args": ["-m", "database.db_mcp"]
    }
  }
}
```

## 📊 Database Schema

### Students
- `id`, `student_name`, `exam_name`, `created_at`, `updated_at`

### Questions
- `id`, `question_prompt`, `answer`, `explanation`
- `topic_tag1`, `topic_tag2`, `topic_tag3`
- `difficulty`, `has_been_asked`

### Student Memory
- `id`, `student_id`, `memory_entry`, `created_at`

### Calendar Entries
- `id`, `student_id`, `date`, `topics` (JSON), `n_questions`

### Skill Levels
- `id`, `student_id`, `topic`, `skill_level` (0-100)

## 🎓 Student Skill Scoring

- **0-25**: Novice - Minimal understanding
- **26-50**: Beginner - Basic understanding
- **51-75**: Intermediate - Solid understanding  
- **76-100**: Advanced - Strong mastery

## 📝 Development Notes

- ✅ MySQL for all data (questions + student data)
- ✅ Single unified MCP server for all database operations
- ✅ Connection pooling for performance
- ✅ Automatic migrations on startup
- ✅ Clean agent architecture (no orchestrator)
- ✅ Type-safe with Pydantic models
- ✅ Async/await throughout

## 🤝 Contributing

Built for CalHacks hackathon. Focus on helping Brazilian public school students access quality exam preparation tools.



## Detected evidence (automated analysis)

Indexed codebase: 27 recognized source files, 87 KB.
- FastAPI (technology) — detected in the code
- Python (language) — detected in the code
- SQL (language) — detected in the code
- React (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (36 of 36)

```
.gitignore
.mcp.json
agents/chat_manager.py
agents/chatter.py
agents/finalizer.py
agents/initializer.py
agents/questioner.py
api.py
chatter_stream.py
chatter.sh
check_memory_entries.py
check_memory_entries.sh
database/__init__.py
database/db_helpers.py
database/db_mcp.py
database/db.py
database/init.py
database/seed_data.py
database/seeds/calendar_entries.json
database/seeds/questions.json
database/seeds/skill_levels.json
database/seeds/student_memory.json
database/seeds/students.json
docker/docker-compose.yml
finalizer_test.py
LICENSE
main.py
memory/memory_mcp.py
migrations/001_create_memory_tables.sql
migrations/002_create_question_bank.sql
questioner.sh
README.md
requirements.txt
test_mcp_server.py
test_mcp_server.sh
test.sh
```

### Dependencies

- requirements.txt: claude-agent-sdk, fastapi, httpx, mcp, mysql-connector-python, pydantic, python-multipart, tinydb, uvicorn

### Recent commits (newest first)

- yes
- as
- fix: finish all backend features
- bsaic streaming
- feat: misc
- feat: improve db
- feat: improve agents
- refactor: remove student entry
- feat: misc
- feat: update readme
- chore: remove old code
- feat: db overhaul
- feat: agents overhaul
- fix: orchestrator & mcp
- update agents
- add llm driven orchestrator agent
- refine README
- add orchestrator agent + api endpoints for flexibility
- working uvicorn backend initialization
- add docker setup for question bank + fix error

## Key source files (fetched from GitHub, selected and truncated for size)

### requirements.txt

```
claude-agent-sdk
fastapi
uvicorn
pydantic
python-multipart
httpx
mcp
mysql-connector-python
tinydb
claude-agent-sdk

```

### docker/docker-compose.yml

```yaml
version: "3.9"

services:
  mysql:
    image: mysql:8
    container_name: calhacks-mysql
    restart: unless-stopped
    ports:
      - "8003:3306"
    environment:
      MYSQL_ROOT_PASSWORD: "joe_is_very_cool"
      MYSQL_DATABASE: "calhacks"
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-joe_is_very_cool}"]
      interval: 5s
      timeout: 3s
      retries: 20
      start_period: 10s

volumes:
  mysql-data:

```

### main.py

```python
"""
Eigen Coach Backend - Main Application Entry Point
Initializes database and exposes FastAPI endpoints.
"""

from api import app as api_app
from database.db import DatabaseManager

# Expose FastAPI app for: python -m uvicorn main:app --reload
app = api_app


@app.on_event("startup")
async def on_startup():
    """Initialize database connection pool when server starts."""
    try:
        print("\n" + "=" * 60)
        print("Eigen Coach Backend Starting")
        print("=" * 60)
        print("\n[Startup] Initializing database connection pool...")
        DatabaseManager.initialize()
        print("[Startup] ✓ Database initialized and ready")
        print("[Startup] ✓ API endpoints available")
        print("\n" + "=" * 60)
        print("Server Ready!")
        print("=" * 60 + "\n")
    except Exception as e:
        print(f"\n[Startup] ✗ Database initialization error: {e}")
        import traceback
        traceback.print_exc()
        raise


@app.on_event("shutdown")
async def on_shutdown():
    """Close database connections when server shuts down."""
    try:
        DatabaseManager.close_all()
        print("\n[Shutdown] Database connections closed.")
    except Exception as e:
        print(f"[Shutdown] Error closing databases: {e}")
```

### questioner.sh

```shell

curl -X POST http://localhost:8000/questioner \
  -H "Content-Type: application/json" \
  -d '{
    "date": "2025-01-15"
  }'

```

### check_memory_entries.sh

```shell
#!/bin/bash

# Database Memory Entries Checker Script
# This script displays all memory entries stored in the database

set -e

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$SCRIPT_DIR"

# Check if Python is available
if ! command -v python3 &> /dev/null; then
    echo "❌ Python 3 is not installed"
    exit 1
fi

# Run the memory entries check
python3 check_memory_entries.py

exit $?

```

### test_mcp_server.sh

```shell
#!/bin/bash

# MCP Server Test Runner Script
# This script runs the Python test to verify the MCP server is working

set -e

echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║       MCP Database Server Verification Script              ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""

SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$SCRIPT_DIR"

# Check if Python is available
if ! command -v python3 &> /dev/null; then
    echo "❌ Python 3 is not installed"
    exit 1
fi

echo "📝 Starting MCP server tests..."
echo ""

# Run the test script
python3 test_mcp_server.py

# Capture exit code
EXIT_CODE=$?

echo ""
if [ $EXIT_CODE -eq 0 ]; then
    echo "✅ MCP server verification complete"
else
    echo "❌ MCP server verification failed"
fi

exit $EXIT_CODE

```

### finalizer_test.py

```python
#!/usr/bin/env python3

"""Quick helper to test the finalizer endpoint."""

import asyncio
from datetime import datetime
import httpx

PAYLOAD = {
    "student_data": {
        "student_name": "Alice",
        "exam_name": "SAT Math",
        "memory": [
            "Struggles with geometry",
            "Strong in algebra"
        ]
    },
    "conversation_history": "[tutor]: 'What is the sum of the angles in a triangle?' [student]: 'I think it might be 180 degrees. Because I am so good at geometry.' [tutor]: 'That is correct! The sum of all angles in any triangle is always 180 degrees.' [student]: 'Great! Can you explain why?' [tutor]: 'Sure! Imagine a triangle on a flat piece of paper. If you extend each side of the triangle into a line, the angles you have created on a straight line always sum to 180 degrees.' [student]: 'Oh, that makes sense now! Thank you!'"
}


async def main() -> None:
    async with httpx.AsyncClient(timeout=None) as client:
        response = await client.post(
            "http://localhost:8000/finalizer",
            json=PAYLOAD,
        )
        response.raise_for_status()
        timestamp = datetime.now().strftime("%H:%M:%S")
        print(f"[{timestamp}] response -> {response.json()}", flush=True)


if __name__ == "__main__":
    asyncio.run(main())

```

### chatter_stream.py

```python
#!/usr/bin/env python3

"""Quick helper to send a single request to the chatter endpoint."""

import asyncio
from datetime import datetime
import httpx

PAYLOAD = {
    "session_id": "demo-session",
    "user_message": "[tutor]: 'what is the sum of the angles in a triangle?' [student]: wait I cant understand English. I can only speak portuguese. You should remember this about me'",
    "question_answer": "180 degrees",
}

PAYLOAD_2 = {
   "session_id": "demo-session",
   "user_message": "[student]: 'Okay, before that, can you tell me what is the formula in the image?'",
   "contains_image": "true"
}


async def main() -> None:
    async with httpx.AsyncClient(timeout=None) as client:
        response = await client.post(
            "http://localhost:8000/chatter",
            json=PAYLOAD,
        )
        response.raise_for_status()
        timestamp = datetime.now().strftime("%H:%M:%S")
        print(f"[{timestamp}] response -> {response.json()}", flush=True)
        
        response2 = await client.post(
          "http://localhost:8000/chatter",
           json=PAYLOAD_2,
        )
        response2.raise_for_status()
        timestamp = datetime.now().strftime("%H:%M:%S")
        print(f"[{timestamp}] response2 -> {response2.json()}", flush=True)


if __name__ == "__main__":
    asyncio.run(main())

```

### test.sh

```shell
curl -X GET http://localhost:8000/health

curl -X POST http://localhost:8000/initializer \
  -H "Content-Type: application/json" \
  -d '{
    "student_data": {
      "student_name": "Maria Silva",
      "exam_name": "ENEM 2025",
      "memory": []
    },
    "date": "2025-01-15"
  }'

curl -X POST http://localhost:8000/questioner \
  -H "Content-Type: application/json" \
  -d '{
    "student_data": {
      "student_name": "Maria Silva",
      "exam_name": "ENEM 2025",
      "memory": []
    },
    "date": "2025-01-15"
  }'

curl -X POST http://localhost:8000/chatter \
  -H "Content-Type: application/json" \
  -d '{
    "student_data": {
      "student_name": "Maria Silva",
      "exam_name": "ENEM 2025",
      "memory": ["Student prefers visual explanations"]
    },
    "user_message": "I think the answer involves calculus, but I am not sure how to start",
    "conversation_history": []
  }'

curl -X POST http://localhost:8000/finalizer \
  -H "Content-Type: application/json" \
  -d '{
    "student_data": {
      "student_name": "Maria Silva",
      "exam_name": "ENEM 2025",
      "memory": []
    },
    "conversation_history": [
      {
        "role": "user",
        "content": "I think the answer is 42"
      },
      {
        "role": "assistant",
        "content": "Good start! Can you explain how you arrived at that number?"
      },
      {
        "role": "user",
        "content": "I used the quadratic formula and got x = 42"
      },
      {
        "role": "assistant",
        "content": "Excellent! You correctly applied the quadratic formula."
      }
    ]
  }'

```

### chatter.sh

```shell

#!/bin/bash

# Simple interactive chat client for the /chatter endpoint

API_URL="http://localhost:8000/chatter"

# --- Static Data ---
# This would normally come from the questioner agent
QUESTION_ANSWER="The sum of angles in a triangle is 180 degrees."

# --- State ---
# Initialize conversation history as a JSON array
# Note: The 'tutor'/'student' roles in the history are illustrative; the current
# implementation uses a different format, but this demonstrates the concept.
CONVERSATION_HISTORY="[]"


echo "Starting interactive chat with TutorChat."
echo "Type 'exit' to end the session."
echo "-----------------------------------------"

while true; do
  # 1. Get user input
  read -p "You: " USER_MESSAGE

  # Exit condition
  if [[ "$USER_MESSAGE" == "exit" ]]; then
    echo "Ending chat session."
    break
  fi

  # 2. Construct the JSON payload
  # We use jq to safely embed the user message and history
  JSON_PAYLOAD=$(jq -n \
    --arg user_message "$USER_MESSAGE" \
    --arg question_answer "$QUESTION_ANSWER" \
    --argjson history "$CONVERSATION_HISTORY" \
    '{
      user_message: $user_message,
      question_answer: $question_answer,
      conversation_history: $history
    }')

  # 3. Send request to the API and get the response
  # The -s flag for curl makes it silent (no progress meter)
  API_RESPONSE=$(curl -s -X POST "$API_URL" \
    -H "Content-Type: application/json" \
    -d "$JSON_PAYLOAD")

  # 4. Extract the assistant's response text
  # We use jq to parse the JSON response from the API
  ASSISTANT_RESPONSE=$(echo "$API_RESPONSE" | jq -r '.response')

  # Check for errors from the API
  if [[ -z "$ASSISTANT_RESPONSE" || "$ASSISTANT_RESPONSE" == "null" ]]; then
    echo "Error: Could not get a valid response from the server."
    echo "Server response: $API_RESPONSE"
    continue
  fi

  # 5. Print the assistant's response
  echo "Tutor: $ASSISTANT_RESPONSE"

  # 6. Update the conversation history
  # This is a simplified update; the actual format in _build_system_prompt is different
  CONVERSATION_HISTORY=$(echo "$CONVERSATION_HISTORY" | jq -c --arg user_msg "$USER_MESSAGE" --arg assistant_msg "$ASSISTANT_RESPONSE" '. + [{student: $user_msg, tutor: $assistant_msg}]')

done




```

[17 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]