FeedForward is designed as a monolithic web application with a focus on simplicity, privacy, and educational effectiveness. The architecture prioritizes ease of deployment, minimal operational overhead, and clear separation of concerns while providing sophisticated AI-powered feedback capabilities.
┌─────────────────────────────────────────────────────────────┐
│ Web Browser │
│ (Student/Instructor/Admin) │
└─────────────────────────┬───────────────────────────────────┘
│ HTTPS
┌─────────────────────────┴───────────────────────────────────┐
│ FastHTML Application │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Presentation Layer │ │
│ │ (FastHTML + HTMX + Tailwind CSS) │ │
│ └─────────────────────────┬───────────────────────────┘ │
│ ┌─────────────────────────┴───────────────────────────┐ │
│ │ Application Layer │ │
│ │ (Routes, Controllers, Business Logic) │ │
│ └─────────────────────────┬───────────────────────────┘ │
│ ┌─────────────────────────┴───────────────────────────┐ │
│ │ Service Layer │ │
│ │ (Auth, Feedback Gen, Email, File Processing) │ │
│ └─────────────────────────┬───────────────────────────┘ │
│ ┌─────────────────────────┴───────────────────────────┐ │
│ │ Data Layer │ │
│ │ (FastLite ORM + SQLite Database) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────┬───────────────────────────────┘
│
┌─────────────────────┴────────────────┐
│ │
┌───────┴─────────┐ ┌─────────┴────────┐
│ External APIs │ │ File Storage │
│ (LLM Providers)│ │ (Local Disk) │
└─────────────────┘ └──────────────────┘
Core Framework:
Language: Python 3.8+
Web Framework: FastHTML (FastAPI + Starlette)
Template Engine: FastHTML components
Frontend: HTMX for interactivity
Styling: Tailwind CSS
Database:
Engine: SQLite 3
ORM: FastLite
Migrations: Custom SQL scripts
External Services:
AI/LLM: LiteLLM (multi-provider)
Email: SMTP
File Processing: Python libraries
Infrastructure:
Server: Uvicorn ASGI
Process Manager: systemd/supervisor
Reverse Proxy: nginx (recommended)
FeedForward follows a modified MVC pattern:
Models (app/models/)
├── User, Course, Assignment, Draft
├── AIModel, FeedbackResult
└── Database schema definitions
Views (app/templates/ + FastHTML components)
├── Server-side rendered HTML
├── HTMX partial updates
└── Tailwind CSS styling
Controllers (app/routes/)
├── Route handlers by role
├── Business logic coordination
└── Response generation
Key services are isolated for maintainability:
Services (app/services/)
├── AuthService - Authentication/authorization
├── FeedbackGenerator - AI feedback orchestration
├── EmailService - Notification delivery
├── FileHandler - Upload/content extraction
├── PrivacyService - Data cleanup/retention
└── AnalyticsService - Progress tracking
-- Core Educational Hierarchy
users (id, email, role, status...)
└── instructors → courses
└── assignments
└── drafts (student submissions)
└── feedback_results
-- AI Configuration
ai_providers (api keys, config)
└── ai_models
└── assignment_model_runs
└── model_feedback_results
-- Supporting Tables
rubric_templates
rubric_criteria
course_students
invitations
audit_logs
Student Submission Flow:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. File Upload
Browser → FastHTML → FileHandler → Disk Storage
2. Content Processing
File → Extractor → Draft Record → Database
3. AI Feedback Generation
Draft → FeedbackGenerator → LLM APIs → Results
4. Instructor Review
Results → Review Interface → Approval → Student
5. Privacy Cleanup
Scheduled Task → Remove Content → Keep Metadata
All major entities follow a consistent lifecycle:
Status Progression:
draft → active → inactive → archived → deleted
Soft Deletion:
- Records never physically deleted
- Status field indicates state
- Deleted_at timestamp when removed
- Cascade rules for dependent entities
Example - Course Lifecycle:
draft: Being created
active: Students can access
inactive: No new submissions
archived: Read-only access
deleted: Hidden from all users
Authentication Flow:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. Email/Password Login
└── Verify credentials
└── Create session
└── Set secure cookie
2. Role-Based Access Control
Admin: Full system access
Instructor: Course management
Student: Assignment submission
3. Route Protection
@admin_required
@instructor_required
@student_required
API Key Management:
Storage: Encrypted in database
Encryption: Fernet (symmetric)
Key Derivation: PBKDF2HMAC from SECRET_KEY
Access: Decrypted only when needed
Student Data Privacy:
Submissions: Temporarily stored
Cleanup: Automatic after feedback
Retention: Metadata only
Access: Student + Instructor only
Session Security:
Cookie: HTTPOnly, Secure, SameSite
Timeout: Configurable (default 24h)
Storage: Server-side sessions
AI Model Hierarchy:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Provider Level (OpenAI, Anthropic, etc.)
├── API Configuration
├── Authentication
└── Rate Limiting
Model Level (GPT-4, Claude-3, etc.)
├── Capabilities
├── Default Parameters
└── Cost Information
Instance Level (Per Assignment Config)
├── Temperature Settings
├── Token Limits
├── Custom Prompts
└── Number of Runs
# Simplified feedback generation flow
async def generate_feedback(draft_id):
# 1. Load assignment configuration
assignment = get_assignment_settings(draft_id)
# 2. Execute multiple model runs
results = []
for model in assignment.models:
for run in range(model.num_runs):
result = await call_llm(
model=model,
prompt=build_prompt(draft, rubric),
parameters=model.parameters
)
results.append(result)
# 3. Aggregate results
final_feedback = aggregate_results(
results,
method=assignment.aggregation_method
)
# 4. Store and notify
save_feedback(final_feedback)
notify_instructor(draft_id)
Concurrent Users: ~1,000 active
Total Users: ~20,000 accounts
Requests/Second: ~100
Database Size: ~10GB practical limit
File Storage: Local disk dependent
Vertical Scaling (Current):
├── Increase server resources
├── Optimize database queries
├── Add caching layer
└── Tune worker processes
Future Horizontal Scaling:
├── PostgreSQL migration
├── S3 file storage
├── Redis session store
├── Load balancer
└── Background job queue
Recommended Production Setup:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌─────────────────────────────────┐
│ nginx (Reverse Proxy) │
│ - SSL termination │
│ - Static file serving │
│ - Rate limiting │
└────────────────┬────────────────┘
│
┌────────────────┴────────────────┐
│ Uvicorn (ASGI Server) │
│ - Multiple workers │
│ - Process management │
└────────────────┬────────────────┘
│
┌────────────────┴────────────────┐
│ FeedForward Application │
│ - FastHTML app │
│ - SQLite database │
│ - Local file storage │
└─────────────────────────────────┘
/opt/feedforward/
├── app/ # Application code
├── data/
│ ├── feedforward.db # SQLite database
│ └── uploads/ # Temporary files
├── logs/ # Application logs
├── backups/ # Database backups
└── venv/ # Python virtual environment
# Background task example
@background_task
async def process_draft_submission(draft_id):
try:
# Update status
update_draft_status(draft_id, "processing")
# Generate feedback
await generate_feedback(draft_id)
# Cleanup
schedule_content_removal(draft_id)
except Exception as e:
update_draft_status(draft_id, "error")
log_error(e)
Scheduled Jobs:
Privacy Cleanup:
Schedule: Every hour
Action: Remove old submission content
Database Optimization:
Schedule: Daily at 2 AM
Action: VACUUM and ANALYZE
Usage Reports:
Schedule: Weekly
Action: Generate analytics
Backup:
Schedule: Daily at 3 AM
Action: Database backup
Cache Layers:
Application Cache:
- Rubric templates
- AI model configurations
- User permissions
Database Query Cache:
- Prepared statements
- Connection pooling
- Index optimization
Frontend Cache:
- Static assets (1 year)
- HTMX partials (5 minutes)
- API responses (varies)
-- Key indexes for performance
CREATE INDEX idx_drafts_assignment_student
ON drafts(assignment_id, student_id);
CREATE INDEX idx_feedback_draft
ON feedback_results(draft_id);
CREATE INDEX idx_courses_instructor
ON courses(instructor_id, status);
-- Partitioning strategy (future)
-- Partition by academic year for historical data
User Errors (4xx):
- Invalid input → Clear error message
- Unauthorized → Redirect to login
- Not found → Helpful 404 page
System Errors (5xx):
- Database error → Retry + fallback
- AI API failure → Queue for retry
- Server error → Error page + logging
External Service Errors:
- LLM timeout → Fallback model
- Email failure → Retry queue
- File processing → Error notification
# Resilient service pattern
async def call_ai_with_fallback(prompt, models):
for model in models:
try:
return await model.generate(prompt)
except (Timeout, APIError):
continue
# All models failed
return queue_for_manual_review(prompt)
Application Metrics:
- Request rate and latency
- Error rates by endpoint
- Active user sessions
- Background job success rate
Business Metrics:
- Submissions per day
- Feedback generation time
- AI API usage and costs
- User engagement rates
System Metrics:
- CPU and memory usage
- Database query performance
- Disk usage and I/O
- Network throughput
Log Streams:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Application Logs → /logs/app.log
- User actions
- Business events
- Errors and warnings
Access Logs → /logs/access.log
- HTTP requests
- Response times
- Status codes
Audit Logs → Database
- Security events
- Data modifications
- Permission changes
AI Logs → /logs/ai_calls.log
- Model calls
- Token usage
- Response times
Near-term (6 months):
- Redis caching layer
- PostgreSQL migration option
- S3-compatible file storage
- Enhanced monitoring (Prometheus)
Medium-term (1 year):
- Microservices extraction
- API-first architecture
- Real-time notifications (WebSockets)
- Advanced analytics pipeline
Long-term (2+ years):
- Multi-tenant SaaS option
- Kubernetes deployment
- Event-driven architecture
- Machine learning pipeline
SQLite → PostgreSQL:
- Export/import scripts
- Connection string change
- Query compatibility layer
- Gradual migration support
Monolith → Services:
- Extract feedback service first
- API gateway pattern
- Shared database initially
- Event bus for communication
Key architectural decisions are documented in ADRs:
This architecture is designed for institutional deployment with 1,000-20,000 users. For larger scales, consider the migration paths outlined above.