An AI-powered backend agent that plugs into the WordBloc game to study kids' learning ability, recommend personalized words, and make the game smarter over time.
| Feature | Description |
|---|---|
| Learning Profiler | Tracks every word attempt, success rate, time taken, and phonics patterns per student |
| Adaptive Difficulty | Auto-adjusts word difficulty in real-time based on session performance |
| Onboarding Diagnostic | Short adaptive diagnostic (max 10 questions) that calibrates starting difficulty and estimates initial phonics struggles |
| Word Recommender | Suggests next words based on phonics gaps, preferred themes, and difficulty level |
| Spaced Repetition | SM-2 algorithm resurfaces forgotten words at optimal intervals (1 → 3 → 7 → 14 days) |
| Struggle Detection | Identifies phonics patterns the kid consistently gets wrong (e.g. digraph-sh, silent-gh) |
| Progressive Hints | 3-level hint system: theme hint → first letter → first + last letter |
| Story Mode | Generates a short 3-sentence story using words the kid just learned (via AWS Bedrock) |
| Encouragement Engine | Detects frustration (3+ consecutive failures) and switches to easier words |
| Parent/Teacher Dashboard | Weekly report with mastered words, weak spots, and actionable recommendations |
| Phonics Neighbors | Finds words that share phonics patterns to build word families |
| Consent Gate | Requires an auditable parent/guardian consent record before any student profile or diagnostic data is created |
| Portable Data Export | Returns raw profile, consent, diagnostic, report, and cached-audio data in one versioned JSON document |
| Full Deletion & Retention | Purges every managed student-data location on request or after configurable inactivity |
| Experimentation Framework | Measures spaced-repetition variants against each other via deterministic A/B assignment (see below) |
eduAgent/
├── agent/
│ ├── profiler.py # Consented student profile tracker (SM-2 spaced repetition)
│ ├── privacy.py # Complete export, deletion, and retention controls
│ ├── recommender.py # Personalized word recommendation engine
│ ├── hint_generator.py # Progressive hints + AWS Bedrock AI hints
│ ├── story_mode.py # Story generator using learned words (AWS Bedrock)
│ └── experiments.py # Variant registry + deterministic student assignment
├── data/
│ ├── word_bank.json # 44 words tagged by difficulty, phonics, theme
│ └── student_profiles/ # Per-student JSON profiles (auto-created)
├── api/
│ └── routes.py # FastAPI REST endpoints
├── dashboard/
│ ├── report.py # Parent/teacher report generator
│ └── experiment_report.py # Per-variant retention/time-to-mastery/engagement metrics
├── tests/
│ ├── test_agent.py # Agent, API, consent, export, delete, and retention tests
│ └── test_experiments.py # Tests for the experimentation framework
├── PRIVACY.md # Data inventory, lifecycle, limits, and legal open questions
├── main.py # FastAPI app entry point + periodic retention sweep
└── requirements.txt
Built to validate the spaced-repetition/difficulty algorithm (see agent/profiler.py) against alternative parameterizations, with honest measurement — not to declare a winner. This is measurement infrastructure only: it does not change _update_spaced_repetition, _compute_difficulty, or any of their thresholds. The pre-existing algorithm is registered as the "control" variant with parameters that are bit-identical to what was previously hardcoded, so control-bucketed students see zero behavior change (enforced by a regression test).
- Every student is deterministically assigned to a variant the first time their profile is created, via a stable hash (
hashlib.sha256, not Python's per-process-randomizedhash()) of theirstudent_idinto a fixed bucket space. The assignment is persisted on the profile (experiment_variantfield) and never recomputed. _update_spaced_repetitionand_compute_difficultyread their constants from the assigned variant's registry entry instead of using hardcoded literals — parameterization only, no logic changes.GET /api/v1/experiments/report(optionally?retention_days=N) returns per-variant retention, time-to-mastery, and session-engagement metrics with sample sizes.POST /api/v1/experiments/report/exportwrites the same report to a JSON file.
The entire change lives in agent/experiments.py — no other file needs to change:
VARIANT_REGISTRY["variant_b_example"] = {
"ef_min": 1.3, "ef_delta": 0.12, "ef_penalty_base": 0.08, "ef_penalty_scale": 0.02,
"failure_interval_days": 1, "first_success_interval_days": 3, "mastery_interval_days": 14,
"difficulty_window": 10, "difficulty_up_threshold": 0.8, "difficulty_down_threshold": 0.4,
"difficulty_min": 1, "difficulty_max": 5,
}
VARIANT_BUCKETS["variant_b_example"] = range(9000, 9500) # carve out of unallocated headroom onlyNever shrink or move an already-allocated variant's bucket range — that would silently reassign its existing students. Always carve new ranges out of currently-unallocated space. This is verified by a test that adds and removes a throwaway variant end-to-end and asserts no other module needed a change.
attempt_logon each profile grows unbounded for the profile's lifetime (needed to reconstruct session boundaries, since no session entity otherwise exists in this data model). Acceptable at this project's current scale (one small JSON file per student); a cap/rotation strategy is future work.- Retention is measured from the most recent recorded review at-or-after the N-day mark, not literally "the first" such review — see
dashboard/experiment_report.pyfor the precise definition. - Time-to-mastery is measured in wall-clock days (
mastered_at - first_seen), not review count, since attempt counts keep growing after mastery and aren't reset/snapshotted at the moment of mastery.
pip3 install -r requirements.txtFor AI-powered hints and story generation, configure AWS credentials:
export AWS_REGION=us-east-1
export AWS_ACCESS_KEY_ID=<your-key>
export AWS_SECRET_ACCESS_KEY=<your-secret>If Bedrock is unavailable, the agent falls back to built-in templates automatically.
# Defaults shown
export DATA_RETENTION_MONTHS=12
export RETENTION_SWEEP_INTERVAL_HOURS=24
export CORS_ALLOW_ORIGINS=http://localhost:3000,http://localhost:5173The service sweeps expired records at startup and periodically thereafter. Read
PRIVACY.md before any deployment involving children; the repository
provides data-lifecycle plumbing but is not, by itself, a complete COPPA/FERPA
compliance program.
python3 -m uvicorn main:app --reloadAPI docs available at: http://localhost:8000/docs
Every /api/v1/* route requires an API key. Each key belongs to an account
(a parent or teacher backend) that owns a fixed set of student_ids — a
teacher's classroom is simply that account's set of students. A caller can only
read or write students it owns; anything else returns 403. Requests with no
key or an unknown key return 401.
Accounts live in data/accounts.json. Keys are stored only as a SHA-256 hash,
so provision a new client by hashing its key and adding an entry:
python3 -c "import hashlib; print(hashlib.sha256(b'YOUR_RAW_KEY').hexdigest())"{
"account_id": "parent_amy",
"role": "parent",
"api_key_sha256": "3088cdcb4617f6b3d519cc705de093eb6ead77401df4f618c3099e9bec6afd98",
"student_ids": ["student_001"]
}A frontend obtains its key out of band (e.g. from the parent/teacher portal) and sends it as a bearer token on every request:
Authorization: Bearer <api_key>The seed store ships with two keys for local testing:
| Key | Owns |
|---|---|
wb_parent_amy_7Qk2Rf9xLm |
student_001 |
wb_teacher_lee_3Zt8Wp1yNc |
student_010, student_011 |
curl -H "Authorization: Bearer wb_parent_amy_7Qk2Rf9xLm" \
http://localhost:8000/api/v1/profile/student_001Rotate these before any non-local deployment.
All /api/v1/* requests below require the Authorization: Bearer <api_key>
header described in Authentication.
No student profile or diagnostic file can be created without consent metadata.
Create the profile explicitly, or include the same consent_metadata on a
first-use attempt, recommendation, story, or diagnostic-start request.
POST /api/v1/profile
Content-Type: application/json{
"student_id": "student_001",
"consent_metadata": {
"guardian_id": "opaque-guardian-123",
"relationship": "parent",
"consent_given": true,
"consent_method": "verified_parent_portal",
"privacy_policy_version": "2026-07-17",
"consented_at": "2026-07-17T10:30:00+00:00"
}
}consented_at may be omitted to use the server receipt time. The consent record
is an audit record only; a production consent UI must independently verify the
adult and satisfy applicable legal requirements.
To calibrate a new student's starting difficulty and phonics struggles without polluting their spaced repetition schedule, the diagnostic flow runs for a maximum of 10 adaptive questions.
POST /api/v1/onboarding/diagnostic/next
{
"student_id": "student_001",
"consent_metadata": {
"guardian_id": "opaque-guardian-123",
"relationship": "parent",
"consent_given": true,
"consent_method": "verified_parent_portal",
"privacy_policy_version": "2026-07-17"
}
}consent_metadata is needed only if this request is creating the profile.
Response:
{
"completed": false,
"student_id": "student_001",
"question_index": 1,
"total_questions": 10,
"active_question": {
"word": "cake",
"difficulty": 3,
"phonics": ["CVCe", "long-a"],
"theme": "food"
}
}POST /api/v1/onboarding/diagnostic/submit
{
"student_id": "student_001",
"word": "cake",
"success": true,
"time_taken_seconds": 4.5
}Response:
{
"completed": false,
"student_id": "student_001",
"word": "cake",
"success": true,
"question_index": 1,
"next_difficulty": 4,
"starting_difficulty": null,
"initial_phonics_struggles": null
}If completed (at 10 questions), completed becomes true and the profile's current_difficulty and phonics_struggles are updated using the calibration result, while keeping the SM-2 learning schedule unpolluted.
POST /api/v1/attempt
{
"student_id": "student_001",
"word": "cat",
"success": true,
"time_taken_seconds": 6.5,
"phonics_tags": ["CVC", "short-a"],
"theme": "animals",
"difficulty": 1
}POST /api/v1/recommend
{ "student_id": "student_001", "count": 5 }POST /api/v1/hint
{ "word": "ship", "theme": "transport", "attempt_number": 1, "use_bedrock": true }POST /api/v1/story
{ "student_id": "student_001", "words": ["cat", "bat", "hat"], "use_bedrock": true }GET /api/v1/report/{student_id}
GET /api/v1/profile/{student_id}/review
GET /api/v1/profile/{student_id}/exportReturns portable export schema 1.0: raw profile and consent, raw diagnostic
session, stored reports, cached-audio files encoded as Base64, and a manifest.
The older report export is only a derived summary and is not a complete export.
DELETE /api/v1/profile/{student_id}Idempotently removes the profile/consent record, diagnostic session, managed reports (including the legacy report location), and student-associated audio cache. The same deletion function is used by automatic retention.
GET /api/v1/neighbors/{word}
GET /api/v1/experiments/report?retention_days=30
POST /api/v1/experiments/report/export
The application uses Python's standard logging module, configured via agent/log_config.py.
| Variable | Default | Description |
|---|---|---|
LOG_LEVEL |
INFO |
One of DEBUG, INFO, WARNING, ERROR, CRITICAL |
LOG_JSON |
0 |
Set to 1, true, yes, or on for newline-delimited JSON output |
When LOG_JSON=1, each log line is a single JSON object:
{"timestamp": "2026-07-20T12:34:56.789Z", "level": "ERROR", "logger": "agent.hint_generator", "message": "Bedrock hint generation failed unexpectedly for word 'cat': 'content'", "module": "agent.hint_generator", "function": "_bedrock_hint", "word": "cat"}This format is ready for ingestion by log aggregators (CloudWatch, ELK, Datadog, etc.).
- agent/hint_generator.py — AWS Bedrock errors logged as
WARNING, unexpected errors (bugs) logged asERROR - agent/story_mode.py — Same two-tier error handling
- api/routes.py — Profile creation, attempts recorded, hint generation
- dashboard/report.py — Report generation and export
- dashboard/experiment_report.py — Experiment report export
- main.py — Startup, retention sweeps, and profile error handlers
All ERROR-level logs are emitted to stdout in a format suitable for monitoring. For production-grade error tracking (e.g. Sentry), pipe stdout to your preferred log aggregator.
python3 -m pytest tests/ -vKid plays WordBloc
│
▼
POST /attempt ──► Profiler records result
│
├── Updates SM-2 spaced repetition schedule
├── Tracks phonics struggle patterns
├── Adjusts difficulty (up if >80% success, down if <40%)
└── Detects frustration (3+ failures → easier words)
│
▼
POST /recommend ──► Recommender scores all unseen words
│
├── Priority 1: Words due for spaced repetition review
├── Priority 2: Words targeting phonics weak spots
├── Priority 3: Words matching preferred themes
└── Priority 4: Words at appropriate difficulty level