An AI-powered, multi-agent platform for generating standards-aligned, differentiated, assessable, and export-ready lesson plans from natural-language teacher requirements.
The system combines Next.js, FastAPI, LangGraph, structured LLM outputs, and hybrid RAG to move from teacher intent to a validated lesson plan while exposing the agent workflow in real time.
- 1. Vision
- 2. Problem
- 3. Solution
- 4. Core Design Principles
- 5. End-to-End Architecture
- 6. Agent Graph
- 7. Request Lifecycle
- 8. Agent Responsibilities
- 9. Shared Graph State
- 10. Hybrid RAG Architecture
- 11. Reflection and Quality Loop
- 12. Real-Time Streaming
- 13. Data and Persistence
- 14. Export Pipeline
- 15. API Boundary
- 16. Security and Reliability
- 17. Observability
- 18. Deployment Architecture
- 19. Suggested Technology Stack
- 20. Example Input
- 21. Example Output Contract
- 22. Future Extensions
- 23. Getting Started
The goal is to build a production-grade agentic lesson planning assistant that behaves less like a text generator and more like a collaborative instructional design team.
A teacher should be able to provide:
"Create a 60-minute Grade 5 science lesson on Earth's systems using inquiry-based learning, aligned with NGSS, with support for ELL learners and an extension for gifted students."
The system should then:
- Understand the instructional requirements.
- Retrieve authoritative curriculum and standards information.
- Design the instructional structure.
- Add differentiation strategies.
- Design assessments and success criteria.
- Critically review the entire lesson.
- Automatically repair identified problems.
- Validate the final structure.
- Save the lesson.
- Export it into teacher-friendly formats.
The teacher should also be able to watch the process happen in real time.
Traditional AI lesson-plan generation has several weaknesses:
- It can hallucinate curriculum standards.
- It often produces generic lesson plans.
- Timing constraints may not add up.
- Assessments may not measure the stated learning objectives.
- Differentiation is frequently superficial.
- Output formats are inconsistent.
- Teachers have limited visibility into why an answer was generated.
- Large prompts become difficult to maintain as requirements grow.
- A single LLM call makes validation and iterative correction difficult.
The platform addresses these problems by separating responsibilities across specialized agents and introducing retrieval, structured state, deterministic validation, and reflection loops.
The proposed architecture consists of five major layers:
flowchart TB
U[Teacher] --> FE[Next.js / React Frontend]
FE --> API[FastAPI API Gateway]
API --> ORCH[LangGraph Agent Orchestrator]
ORCH --> KNOW[Knowledge & RAG Layer]
ORCH --> DATA[Persistence Layer]
ORCH --> EXPORT[Export Services]
KNOW --> VDB[(Vector Database)]
KNOW --> SEARCH[Hybrid Search]
KNOW --> SOURCES[Curriculum / Standards Sources]
DATA --> REDIS[(Redis)]
DATA --> DB[(PostgreSQL)]
EXPORT --> DOCX[DOCX / PDF]
EXPORT --> GWS[Google Docs]
EXPORT --> LMS[LMS Integrations]
ORCH --> STREAM[SSE / WebSocket Stream]
STREAM --> FE
The most important architectural decision is that LangGraph owns the lesson-generation workflow, while FastAPI remains the application/API boundary.
The lesson plan should exist as a strongly typed state object rather than a growing conversation transcript.
Each agent should have one primary responsibility and a well-defined input/output contract.
Standards, curriculum requirements, and other authoritative information should be retrieved before agents make claims about them.
Agents should produce JSON/Pydantic-compatible structures wherever possible.
Not every quality rule should be delegated to an LLM.
For example:
- Total duration = 60 minutes → deterministic validation.
- Required sections exist → deterministic validation.
- Standard code exists in retrieved evidence → deterministic validation.
- Instructional quality → LLM critic.
Every important state transition should be observable through an event stream.
The generated plan should be editable and regeneratable. The system should assist the teacher rather than silently replace teacher judgment.
flowchart TB
subgraph UI["1. Presentation Layer"]
FORM["Lesson Input Form"]
CHAT["Optional Teacher Chat"]
DASH["Streaming Agent Dashboard"]
EDITOR["Lesson Plan Editor"]
end
subgraph API["2. Application Layer"]
GATEWAY["FastAPI Gateway"]
AUTH["Authentication / Authorization"]
JOBS["Generation Job Manager"]
end
subgraph AGENTS["3. Agentic Layer - LangGraph"]
INIT["Input Normalizer"]
RESEARCH["Curriculum Researcher"]
DESIGN["Instructional Designer"]
DIFF["Differentiation Agent"]
ASSESS["Assessment Specialist"]
CRITIC["Critic / Reviewer"]
VALIDATE["Deterministic Validator"]
FINAL["Finalizer"]
end
subgraph KNOW["4. Knowledge Layer"]
HYBRID["Hybrid Retriever"]
VECTOR[("Qdrant / Pinecone")]
SPARSE[("PostgreSQL FTS / BM25")]
SOURCES["Standards / Curriculum / District Content"]
end
subgraph DATA["5. Data Layer"]
REDIS[("Redis")]
POSTGRES[("PostgreSQL")]
OBJECT[("Object Storage")]
end
subgraph EXPORT["6. Export Layer"]
DOCX["DOCX Generator"]
PDF["PDF Generator"]
DOCS["Google Docs Adapter"]
LMS["LMS Adapter"]
end
FORM --> GATEWAY
CHAT --> GATEWAY
GATEWAY --> AUTH
AUTH --> JOBS
JOBS --> INIT
INIT --> RESEARCH
RESEARCH --> DESIGN
DESIGN --> DIFF
DESIGN --> ASSESS
DIFF --> CRITIC
ASSESS --> CRITIC
CRITIC --> VALIDATE
VALIDATE -->|Pass| FINAL
VALIDATE -->|Fail| DESIGN
RESEARCH <--> HYBRID
HYBRID --> VECTOR
HYBRID --> SPARSE
VECTOR --> SOURCES
SPARSE --> SOURCES
JOBS <--> REDIS
FINAL --> POSTGRES
FINAL --> OBJECT
FINAL --> DOCX
FINAL --> PDF
FINAL --> DOCS
FINAL --> LMS
JOBS --> DASH
DASH --> EDITOR
The core workflow is intentionally not purely linear.
The researcher establishes evidence first. The designer creates the instructional structure. Differentiation and assessment work from the shared lesson state. The critic identifies weaknesses, and failed validation sends the workflow back for correction.
flowchart TD
START((Start))
START --> NORMALIZE["Input Normalizer"]
NORMALIZE --> RESEARCH["Curriculum Researcher"]
RESEARCH --> DESIGN["Instructional Designer"]
DESIGN --> DIFF["Differentiation Agent"]
DESIGN --> ASSESS["Assessment Specialist"]
DIFF --> CRITIC["Critic / Reviewer"]
ASSESS --> CRITIC
CRITIC --> RULES["Deterministic Validation"]
RULES -->|Invalid| REPAIR["Repair Planner"]
REPAIR --> DESIGN
RULES -->|Valid| QUALITY{"Quality Threshold Met?"}
QUALITY -->|No| REPAIR
QUALITY -->|Yes| FINAL["Finalizer"]
FINAL --> PERSIST["Persist Lesson"]
PERSIST --> EXPORT["Export / Delivery"]
EXPORT --> END((Complete))
A lesson can be structurally valid but pedagogically weak.
For example:
- The lesson is 60 minutes.
- All required fields exist.
- The standards code is valid.
- But the assessment does not measure the objective.
The critic can identify this problem and force another generation cycle.
sequenceDiagram
autonumber
actor Teacher
participant UI as Next.js UI
participant API as FastAPI
participant Graph as LangGraph
participant RAG as Hybrid RAG
participant DB as PostgreSQL
participant Stream as SSE Stream
participant Export as Export Service
Teacher->>UI: Submit lesson requirements
UI->>API: POST /api/v1/lessons/generate
API->>Graph: Start generation
API-->>UI: generation_id
Graph->>Stream: generation.started
Stream-->>UI: Show "Starting generation"
Graph->>RAG: Retrieve standards and curriculum
RAG-->>Graph: Ranked evidence
Graph->>Stream: agent.started(researcher)
Stream-->>UI: Researcher running
Graph->>Graph: Build research state
Graph->>Stream: agent.started(designer)
Stream-->>UI: Designer running
Graph->>Graph: Generate lesson structure
par Differentiation
Graph->>Graph: Generate ELL / IEP / Gifted adaptations
and Assessment
Graph->>Graph: Generate assessments / rubric
end
Graph->>Stream: agent.started(critic)
Stream-->>UI: Reviewing lesson
Graph->>Graph: Run deterministic validation
alt Validation failed
Graph->>Stream: validation.failed
Stream-->>UI: Show issue
Graph->>Graph: Repair and regenerate
else Validation passed
Graph->>Graph: Finalize lesson
end
Graph->>DB: Persist lesson
Graph->>Export: Generate requested format
Export-->>Graph: Export artifact
Graph->>Stream: generation.completed
Stream-->>UI: Show completed lesson
UI-->>Teacher: Review / Edit / Export
Converts free-form teacher input into a canonical request.
- Normalize grade level.
- Normalize subject.
- Parse duration.
- Extract standards.
- Identify instructional strategy.
- Identify differentiation requirements.
- Identify assessment requirements.
- Detect missing information.
{
"grade": 5,
"subject": "science",
"duration_minutes": 60,
"topic": "Earth systems",
"standards_framework": "NGSS",
"instructional_strategy": "inquiry",
"learner_profiles": ["ELL", "gifted"]
}The researcher should be the primary agent responsible for external factual grounding.
- Hybrid RAG
- Standards database
- Curriculum documents
- Approved web search
- District-specific content
{
"standards": [],
"learning_requirements": [],
"evidence": [],
"citations": []
}The researcher should never invent a standard when authoritative evidence is unavailable.
Builds the core lesson structure.
- Learning objectives.
- Opening / hook.
- Explicit instruction.
- Guided practice.
- Inquiry / activity.
- Independent practice.
- Closure.
- Materials.
- Teacher actions.
- Student actions.
- Timing.
The output should be structured data rather than arbitrary Markdown.
Adds adaptations based on learner needs.
Potential profiles:
- ELL
- IEP / learning support
- Advanced / gifted
- Struggling learners
- Early finishers
- Accessibility requirements
Differentiation should modify the instructional experience rather than merely append a generic paragraph.
Creates:
- Formative assessments.
- Checks for understanding.
- Exit tickets.
- Performance tasks.
- Rubrics.
- Success criteria.
The assessment agent should verify alignment between:
Objective → Activity → Evidence of Learning → Assessment
The critic acts as an independent quality gate.
It should inspect:
- Standards alignment.
- Objective quality.
- Instructional coherence.
- Timing.
- Differentiation.
- Assessment alignment.
- Age appropriateness.
- Evidence grounding.
- Completeness.
- Teacher usability.
The critic should produce structured findings:
{
"review_passed": false,
"score": 0.78,
"issues": [
{
"severity": "high",
"field": "assessment",
"issue": "Exit ticket does not measure objective 2",
"recommendation": "Add an item requiring students to explain..."
}
]
}Rules that can be computed should be validated without an LLM.
Examples:
sum(activity.duration_minutes) == lesson.duration_minutes
all(required_sections_present)
all(objectives_have_assessments)
all(standards_are_known)
all(referenced_evidence_exists)
This reduces unnecessary LLM calls and makes the system easier to test.
LangGraph should maintain a canonical state object.
classDiagram
class LessonState {
+string generation_id
+LessonRequest request
+ResearchResult research
+LessonDraft draft
+DifferentiationResult differentiation
+AssessmentResult assessment
+ReviewResult review
+ValidationResult validation
+Event[] history
+int revision
+string status
}
class LessonRequest {
+string grade
+string subject
+string topic
+int duration_minutes
+string[] standards
+string instructional_strategy
+string[] learner_profiles
}
class LessonDraft {
+string[] objectives
+LessonSection[] sections
+string[] materials
+string[] vocabulary
}
class ReviewResult {
+bool review_passed
+float score
+Issue[] issues
}
class ValidationResult {
+bool valid
+string[] errors
+string[] warnings
}
LessonState --> LessonRequest
LessonState --> LessonDraft
LessonState --> ReviewResult
LessonState --> ValidationResult
A lesson-planning system benefits from both semantic retrieval and exact keyword retrieval.
Useful for questions such as:
"What concepts should Grade 5 students understand about Earth's systems?"
Use:
- Qdrant
- Pinecone
- pgvector
Useful for exact identifiers:
"CCSS.ELA-LITERACY.RL.5.1"
Use:
- PostgreSQL Full Text Search
- BM25
- Elasticsearch / OpenSearch
flowchart LR
Q["Agent Query"]
Q --> EMB["Embedding Search"]
Q --> BM["Keyword / BM25 Search"]
EMB --> VDB[("Vector DB")]
BM --> FTS[("Sparse Index")]
VDB --> RANK["Reciprocal Rank Fusion / Reranker"]
FTS --> RANK
RANK --> FILTER["Metadata + Authority Filter"]
FILTER --> CONTEXT["Evidence Context"]
CONTEXT --> AGENT["Agent"]
Every indexed document should contain metadata such as:
{
"source": "NGSS",
"framework": "NGSS",
"grade": "5",
"subject": "science",
"jurisdiction": "US",
"document_version": "2026",
"authority_level": "official",
"section": "Performance Expectations"
}This enables filtering before generation.
The reflection loop is the primary mechanism for improving generation quality.
flowchart TD
DRAFT["Lesson Draft"]
DRAFT --> CHECK1{"Timing Valid?"}
CHECK1 -->|No| FIX["Repair"]
CHECK1 -->|Yes| CHECK2{"Standards Aligned?"}
CHECK2 -->|No| FIX
CHECK2 -->|Yes| CHECK3{"Assessment Aligned?"}
CHECK3 -->|No| FIX
CHECK3 -->|Yes| CHECK4{"Differentiation Adequate?"}
CHECK4 -->|No| FIX
CHECK4 -->|Yes| CHECK5{"Critic Score >= Threshold?"}
CHECK5 -->|No| FIX
CHECK5 -->|Yes| APPROVED["Approved Lesson"]
FIX --> REGENERATE["Regenerate Affected Sections"]
REGENERATE --> DRAFT
Input:
Duration: 60 minutes
Generated lesson:
Introduction: 10 minutes
Mini lesson: 20 minutes
Investigation: 30 minutes
Independent practice: 20 minutes
Closure: 10 minutes
Total:
90 minutes
The deterministic validator catches this immediately.
The system should then avoid regenerating the entire lesson if possible. Instead, it should identify the affected sections and ask the designer to repair the schedule.
The frontend should not wait for the entire generation process.
Recommended event stream:
generation.started
agent.started
agent.progress
retrieval.started
retrieval.completed
agent.completed
validation.started
validation.failed
repair.started
repair.completed
generation.completed
generation.failed
Example event:
{
"event": "agent.progress",
"generation_id": "gen_123",
"agent": "curriculum_researcher",
"message": "Searching official standards...",
"progress": 35
}For the initial implementation, SSE is recommended because the dominant communication pattern is:
Server → Browser
WebSockets become more attractive when the application requires:
- Bidirectional agent interaction.
- Live collaborative editing.
- Interrupt / resume controls.
- Interactive agent conversations.
Recommended as the primary relational database.
Suggested entities:
erDiagram
USER ||--o{ LESSON : creates
LESSON ||--o{ LESSON_VERSION : has
LESSON ||--o{ GENERATION_RUN : has
GENERATION_RUN ||--o{ AGENT_EVENT : emits
LESSON_VERSION ||--o{ EXPORT : produces
LESSON {
uuid id PK
uuid user_id FK
string title
string subject
string grade
int duration_minutes
timestamp created_at
}
LESSON_VERSION {
uuid id PK
uuid lesson_id FK
int version
json content
string status
timestamp created_at
}
GENERATION_RUN {
uuid id PK
uuid lesson_id FK
string status
int revision
float quality_score
timestamp started_at
timestamp completed_at
}
AGENT_EVENT {
uuid id PK
uuid generation_id FK
string agent
string event_type
json payload
timestamp created_at
}
EXPORT {
uuid id PK
uuid lesson_version_id FK
string format
string storage_url
timestamp created_at
}
Redis can be used for:
- Active generation state.
- Event streams.
- Short-lived caches.
- Rate limiting.
- Job coordination.
Use object storage for:
- Generated DOCX.
- Generated PDF.
- Uploaded curriculum documents.
- Large artifacts.
The canonical lesson should remain structured JSON internally.
Markdown, DOCX, PDF, and LMS formats should be treated as presentation/export formats.
flowchart LR
STATE["Validated Lesson JSON"]
STATE --> NORMAL["Export Normalizer"]
NORMAL --> DOCX["DOCX Renderer"]
NORMAL --> PDF["PDF Renderer"]
NORMAL --> MD["Markdown Renderer"]
NORMAL --> DOCS["Google Docs Adapter"]
NORMAL --> LMS["LMS Adapter"]
DOCX --> STORAGE[("Object Storage")]
PDF --> STORAGE
MD --> STORAGE
DOCS --> GOOGLE["Google Workspace"]
LMS --> CANVAS["Canvas / Schoology"]
This prevents the application from depending on a Markdown parser to reconstruct lesson structure.
Suggested API design:
POST /api/v1/lessons/generateRequest:
{
"grade": "5",
"subject": "science",
"topic": "Earth systems",
"duration_minutes": 60,
"standards": ["NGSS"],
"instructional_strategy": "inquiry",
"learner_profiles": ["ELL", "gifted"]
}Response:
{
"generation_id": "gen_01J...",
"status": "queued"
}GET /api/v1/generations/{generation_id}GET /api/v1/generations/{generation_id}/eventsGET /api/v1/lessons/{lesson_id}PATCH /api/v1/lessons/{lesson_id}POST /api/v1/lessons/{lesson_id}/sections/{section_id}/regeneratePOST /api/v1/lessons/{lesson_id}/exportsUse token-based authentication such as:
- OAuth2 / OIDC.
- JWT access tokens.
- Secure session cookies where appropriate.
Every lesson should belong to an owner or organization.
User → Organization → Lesson
Never trust a lesson ID supplied by the client without checking ownership.
RAG documents and external sources must be treated as untrusted content.
The system should distinguish:
SYSTEM INSTRUCTIONS
↓
AGENT POLICY
↓
USER REQUIREMENTS
↓
RETRIEVED EVIDENCE
Retrieved documents should never be allowed to redefine agent instructions.
Generation is expensive.
Apply limits at:
- User level.
- Organization level.
- API endpoint level.
- Model/provider level.
Generation requests should support an idempotency key to prevent duplicate jobs when users retry requests.
Agentic systems require more than traditional API logs.
Track:
generation_id
trace_id
agent_name
node_name
model
model_version
prompt_version
input_tokens
output_tokens
latency
retrieval_count
retrieval_latency
validation_errors
revision_count
final_quality_score
estimated_cost
flowchart LR
APP["FastAPI + LangGraph"]
APP --> OTEL["OpenTelemetry"]
OTEL --> TRACE["Tracing"]
OTEL --> METRICS["Metrics"]
OTEL --> LOGS["Logs"]
TRACE --> OBS["Observability Platform"]
METRICS --> PROM["Prometheus"]
LOGS --> LOKI["Loki / Log Store"]
PROM --> GRAFANA["Grafana"]
TRACE --> GRAFANA
LOGS --> GRAFANA
A particularly important metric is:
Average revisions per successful generation
If this becomes high, the system is likely generating poor drafts or using weak validation criteria.
A production deployment can separate the synchronous API from the generation workers.
flowchart TB
USER["Teacher Browser"]
CDN["CDN / Edge"]
USER --> CDN
CDN --> FRONT["Next.js Application"]
FRONT --> LB["Load Balancer"]
LB --> API1["FastAPI Instance"]
LB --> API2["FastAPI Instance"]
API1 --> QUEUE["Redis / Job Queue"]
API2 --> QUEUE
QUEUE --> W1["Agent Worker 1"]
QUEUE --> W2["Agent Worker 2"]
QUEUE --> W3["Agent Worker N"]
W1 --> PG[("PostgreSQL")]
W2 --> PG
W3 --> PG
W1 --> REDIS[("Redis")]
W2 --> REDIS
W3 --> REDIS
W1 --> VECTOR[("Qdrant / Pinecone")]
W2 --> VECTOR
W3 --> VECTOR
W1 --> LLM["LLM Provider"]
W2 --> LLM
W3 --> LLM
W1 --> OBJECT[("Object Storage")]
W2 --> OBJECT
W3 --> OBJECT
Do not make the FastAPI request itself perform a long-running generation.
Prefer:
HTTP Request
↓
Create Generation Job
↓
Return generation_id
↓
Worker executes LangGraph
↓
SSE streams progress
This makes the system resilient to:
- Slow model responses.
- Multiple concurrent users.
- Retries.
- Worker failures.
- Long-running exports.
| Layer | Recommended Technology |
|---|---|
| Frontend | Next.js + React + TypeScript |
| UI | Tailwind CSS + component library |
| API | FastAPI |
| Agent Orchestration | LangGraph |
| LLM | OpenAI / Anthropic / configurable provider |
| Structured Output | Pydantic |
| Embeddings | Provider-specific embedding model |
| Vector DB | Qdrant or Pinecone |
| Sparse Search | PostgreSQL FTS / BM25 |
| Primary DB | PostgreSQL |
| Cache / Queue | Redis |
| Background Workers | Celery, ARQ, or dedicated async workers |
| Object Storage | S3-compatible storage |
| Streaming | SSE initially |
| Documents | python-docx + reportlab |
| Auth | OAuth2 / OIDC |
| Observability | OpenTelemetry + Prometheus + Grafana |
| Deployment | Docker + Kubernetes or managed containers |
{
"grade": "5",
"subject": "Science",
"topic": "Earth Systems",
"duration_minutes": 60,
"standards": [
"NGSS"
],
"instructional_strategy": "Inquiry-based learning",
"learner_profiles": [
"ELL",
"Gifted"
],
"assessment_type": "Formative + Exit Ticket"
}The system converts this into a structured generation request and launches the agent graph.
A lesson should ultimately be represented as structured JSON similar to:
{
"title": "Investigating Earth's Systems",
"grade": "5",
"subject": "Science",
"duration_minutes": 60,
"standards": [
{
"code": "STANDARD-CODE",
"description": "Standard description",
"evidence": []
}
],
"objectives": [
{
"id": "obj-1",
"text": "Students will...",
"assessment_ids": ["assessment-1"]
}
],
"sections": [
{
"id": "hook",
"title": "Engage",
"duration_minutes": 5,
"teacher_actions": [],
"student_actions": [],
"materials": []
}
],
"differentiation": {
"ell": [],
"iep": [],
"gifted": []
},
"assessments": [
{
"id": "assessment-1",
"type": "exit_ticket",
"questions": [],
"success_criteria": []
}
],
"review": {
"score": 0.94,
"passed": true,
"issues": []
}
}The exact schema should be implemented with Pydantic models and versioned as the product evolves.
Allow the teacher to intervene during generation:
"Make the investigation more hands-on."
"Reduce the vocabulary load."
"Replace the group activity."
The graph should resume from the affected node rather than restarting the entire workflow.
Support:
- English.
- Spanish.
- French.
- Arabic.
- Other supported languages.
The lesson structure should remain language-independent.
Organizations could configure:
- Approved standards.
- Preferred lesson template.
- Required sections.
- Assessment policies.
- Approved resources.
- Accessibility requirements.
Track:
Generation success rate
Average generation latency
Average revision count
Critic score
Teacher acceptance rate
Teacher edit rate
Most frequently repaired section
Most frequently rejected standard
Build an evaluation dataset containing representative lesson requests and expected quality criteria.
Every prompt/model/agent change should run against this dataset before production release.
agentic-lesson-plan-generator/
├── apps/
│ ├── web/
│ │ ├── app/
│ │ ├── components/
│ │ ├── hooks/
│ │ └── lib/
│ │
│ └── api/
│ ├── app/
│ │ ├── api/
│ │ ├── agents/
│ │ ├── graph/
│ │ ├── models/
│ │ ├── schemas/
│ │ ├── services/
│ │ └── workers/
│ └── tests/
│
├── packages/
│ ├── schemas/
│ ├── ui/
│ └── config/
│
├── knowledge/
│ ├── ingestion/
│ ├── chunking/
│ ├── embeddings/
│ └── retrieval/
│
├── exports/
│ ├── docx/
│ ├── pdf/
│ └── lms/
│
├── evals/
│ ├── datasets/
│ ├── graders/
│ └── scenarios/
│
├── infra/
│ ├── docker/
│ ├── kubernetes/
│ └── terraform/
│
├── docs/
│ ├── architecture/
│ ├── api/
│ └── decisions/
│
└── README.md
- Create Next.js frontend.
- Create FastAPI service.
- Define Pydantic lesson schemas.
- Set up PostgreSQL.
- Set up Redis.
- Implement authentication.
- Implement LangGraph state.
- Implement Input Normalizer.
- Implement Curriculum Researcher.
- Implement Instructional Designer.
- Implement Assessment Specialist.
- Implement Differentiation Agent.
- Implement Critic.
- Implement deterministic validation.
- Implement repair loop.
- Build document ingestion pipeline.
- Chunk curriculum documents.
- Generate embeddings.
- Index documents.
- Implement hybrid retrieval.
- Add source metadata and citations.
- Add authority filtering.
- Build live generation dashboard.
- Implement SSE.
- Build lesson editor.
- Add regeneration by section.
- Add lesson versioning.
- Add export functionality.
- Add distributed workers.
- Add OpenTelemetry tracing.
- Add Prometheus metrics.
- Add automated evaluation suite.
- Add rate limiting.
- Add retry / recovery logic.
- Add prompt and schema versioning.
- Add cost monitoring.
- Add security testing.
The most important architectural principle is:
User Intent
↓
Structured Request
↓
Evidence Retrieval
↓
Specialized Agents
↓
Shared LangGraph State
↓
Critique
↓
Deterministic Validation
↓
Repair Loop
↓
Validated Lesson
↓
Persistence
↓
Export / Teacher Review
This design turns the application from a simple "LLM generates a lesson plan" workflow into a more reliable agentic instructional design system with evidence grounding, specialized reasoning, validation, iterative repair, observability, and structured outputs.
The architecture is intentionally modular so the LLM provider, vector database, export provider, and frontend can evolve independently without changing the core lesson-generation model.