The Extensible Core: Handler Trio Architecture
The Basic Pattern
Once I understood the handler trio pattern, everything fell into place. What started as a way to build Movement 1 became the architecture for the entire RTS system. Deep Dives use the same pattern. Future movements will use the same pattern. The reflection journal uses it. Black Box Micro-Engagements use it.
The handler trio leans into the extensible philosophy of RTS.
This document tells the story of that pattern: what it is, why it matters, and how you can use it to create your own "Socratic engines."
The Learning Journey: From Confusion to Clarity
The Problem I Was Solving
As someone learning to code while building an educational tool, I kept hitting the same wall: how do I create meaningful AI interactions that are:
- Pedagogically rigorous (not just chatbot vibes)
- Transparent and auditable (everything recorded)
- Repeatable and scalable (not custom code for every feature)
- Actually extensible (not brittle one-offs)
I was drowning in redundant database schemas and unmanageable code. Each new feature felt like starting from scratch.
The Pattern That Emerged
Then I built Movement 1's reflection system and discovered something: three API handlers working together in a predictable flow.
Generate → Save → Synthesize
This could be viewed as the computational expression of a pedagogical philosophy:
- Generate questions based on student thinking (not AI knowledge)
- Save the journey with complete observability
- Synthesize the arc to reveal patterns
Once I saw this pattern, I realized: this IS RTS. Everything else is variations on this theme.
The Handler Trio Pattern Explained
The Three Handlers
Every thematic interaction in RTS follows this structure:
Handler 1: Generate
Purpose: Create AI-generated questions based on student context
What it does:
- Receives student context from database (their research topic, previous reflections)
- Calls Gemini API with:
- Carefully designed system instruction that lives outside the code (the pedagogy)
- Structured JSON schema (the output format)
- Student's own words only (no external knowledge)
- Returns questions + AI metadata (tokens, thought summaries, model used)
- Logs everything to database
Example: generateFollowupQuestions.ts
// Real example from generateSparkFollowupQuestions.ts
// Build reflection trail from previous Deep Dive rounds
let trail = "";
if (round_number > 1) {
const { data: previousReflections } = await supabase
.from("deep_dive_reflection_rounds")
.select("user_reflection")
.eq("session_id", session_id)
.eq("user_id", user.id)
.eq("category_selected", "Spark of Inquiry")
.order("round_number", { ascending: true });
trail = (previousReflections || [])
.map((r, i) => `Round ${i + 1}: ${r.user_reflection}`)
.join("\n\n");
}
// First round vs subsequent rounds
const isFirstRound = round_number === 1 || !trail;
const userContext = isFirstRound
? `Topic: ${sessionData.topic_text}
This is the student's first deep dive. Generate questions focused on their personal connection.`
: `Topic: ${sessionData.topic_text}
Reflection Trail:
${trail}
Generate follow-up questions based on their previous reflections.`;
// Call Gemini with structured schema
const config = {
thinkingConfig: {
thinkingBudget: -1,
includeThoughts: true,
},
responseMimeType: 'application/json',
responseSchema: SparkQuestionsSchema,
systemInstruction: [{ text: GEMINI_SPARK_OF_INQUIRY_SYSTEM_INSTRUCTION }],
};
const result = await ai.models.generateContent({
model: ACTIVE_MODEL,
config,
contents: [{ role: 'user', parts: [{ text: userContext }] }],
});
// Save questions with subcategory support
const questionsToInsert = Object.entries(sparkQuestionsData)
.map(([subcategory, questionText]) => ({
session_id,
user_id: user.id,
category: "Spark of Inquiry",
subcategory: subcategory, // Enables fine-grained organization
question_text: String(questionText),
model_used: ACTIVE_MODEL,
}));
await supabase
.from("deep_dive_followup_questions_log")
.insert(questionsToInsert);
Handler 2: Save
Purpose: Capture student reflection with complete AI metadata
What it does:
- Validates session ownership (security)
- Links student response to specific AI-generated question
- Stores comprehensive metadata (tokens, summaries, model)
- Returns success + next round info
Example: saveFollowupRound.ts
// Validate ownership
const { data: session } = await supabase
.from('rts_sessions')
.select('user_id')
.eq('session_id', session_id)
.single();
if (session.user_id !== user.id) {
return res.status(403).json({ error: 'Forbidden' });
}
// Save reflection with metadata
await supabase.from('reflection_rounds').insert({
session_id,
round_number,
user_reflection: student_answer,
answered_question_id: question_id,
prompt_token_count,
response_token_count,
thinking_token_count,
thought_summary,
model_used
});
Handler 3: Synthesize
Purpose: Generate computational reorganization of entire journey
What it does:
- Fetches all rounds from database
- Sends complete journey to AI
- Requests markdown synthesis (not JSON)
- Stores synthesis with metadata
Example: generateSynthSummary.ts
// Fetch complete journey
const rounds = await supabase
.from('reflection_rounds')
.select('*')
.eq('session_id', session_id)
.order('round_number');
// Build synthesis prompt
const prompt = `
Based on these reflections:
${rounds.map(r => r.user_reflection).join('\n\n')}
Synthesize their journey...
`;
// Generate synthesis
const synthesis = await model.generateContent({
contents: [{ role: "user", parts: [{ text: prompt }] }],
generationConfig: {
responseMimeType: "text/plain"
}
});
// Save to database
await supabase.from('movement_synthesis').insert({
session_id,
synthesis_text: synthesis,
total_token_count,
thought_summary
});
The Power in the Patter
1. Pedagogy Lives in Prompts
The system instructions live outside the API handlers. The code is just infrastructure.
This means:
- Changing pedagogy = updating prompts, not rewriting handlers
- Testing new approaches = experimenting with instructions
- Sharing RTS = sharing prompts + pattern, not custom code
2. The Database Is the Memory
Everything is recorded. This creates:
- Complete audit trails for research
- Observability for AI literacy
- Context for subsequent interactions
- Exportable student portfolios
3. The Pattern Scales Infinitely
Once you understand this flow, you can create ANY thematic interaction:
Want a new Deep Dive category?
- Write a pedagogical prompt
- Define a JSON schema for questions
- Clone the handler trio
- Update the prompt and schema
- Done.
Want to add a new Movement? Same pattern. Different pedagogy.
Want to create a custom interaction for your course? Same pattern. Your pedagogy.
How Deep Dives Prove Extensibility
Deep Dives are the proof of concept for this pattern's flexibility.
The Problem
Students complete Movement 1 (4 rounds on their research topic). But they want to explore deeper through different analytical lenses.
The Solution
Six Deep Dive categories, each using the exact same handler trio pattern:
- Spark of Inquiry - Personal connection lens
- Inquiry as Story - Narrative structure lens
- Stakes and Significance - Public relevance lens
- Puzzles and Unknowns - Research process lens
- Listeners and Lens - Audience awareness lens
- Production Lab - Making as inquiry lens
Each category has:
- Its own pedagogical prompt (the unique lens)
- Its own question subcategories (7 instead of 5)
- Its own synthesis approach (category-specific insights)
But the technical pattern is identical:
generateSparkFollowupQuestions.ts
saveSparkFollowupRound.ts
generateSparkSynthesis.ts
generateInquiryAsStoryFollowupQuestions.ts
saveInquiryAsStoryFollowupRound.ts
generateInquiryAsStorySynthesis.ts
// etc for all 6 categories
The Database Supports All of Them
No schema changes needed. The same tables support all categories:
deep_dive_followup_questions_log(category field differentiates)deep_dive_reflection_rounds(links to questions)deep_dive_synthesis(category-specific summaries)
This is the extensibility in action.
The Recipe: Creating Your Own Thematic Interaction
Here's how to use this pattern to create a new analytical lens, movement, or interaction:
Step 1: Define Your Pedagogy
What conceptual work do you want students to do?
Example from "Puzzles and Unknowns" Deep Dive:
- Help students surface contradictions
- Name methodological gaps
- Identify missing perspectives
- Reflect on shifts in understanding
Step 2: Design Your Questions
What subcategories or question types serve this pedagogy?
Example subcategories:
- The Unexpected Discovery
- The Methodological Gap
- The Missing Voice
- The Productive Confusion
- The "I Used to Think..." Moment
- The Next Question
- Narrating the "How"
Step 3: Write Your Prompts
Create the system instruction for question generation:
You are helping a student explore puzzles and unknowns in their research.
Based on their topic and reflections, generate questions that:
- Surface contradictions and tensions
- Highlight methodological limitations
- Identify absent perspectives
- Encourage productive confusion
- Prompt reflection on learning shifts
Questions should be:
- Specific to their work
- Open-ended and thought-provoking
- Focused on the research PROCESS, not just content
Create the synthesis instruction:
// Real example from Spark synthesis - note W&M-specific customization
You are a mentor speaking directly to a first year student at William & Mary
who is transforming their inquiry about "big ideas" into a compelling
research-based audio essay (podcast episode).
COLL 100 CONTEXT: This student is exploring significant questions that
shape understanding in their discipline.
Output Format:
- Your Intellectual Journey
- Recurring Themes & Narrative Threads
- Productive Tensions
- What Makes Your Thinking Distinctive
- From Spark to Scene: Voicing Your Curiosity
- Strategic Next Steps
* W&M Resources: Consult with Swem Library's Research librarians...
Connect with the Reeder Media Center (mediahelp@wm.edu)...
- Question to Deepen Your Thinking
Customization opportunities:
- Institution-specific resources (W&M Libraries)
- Course-specific context (COLL 100 at W&M)
- Discipline-specific language
- Output format tailored to your pedagogy
- Tone adjusted for your student population
The prompts ARE your pedagogy. Change the prompts, change the educational experience—without touching a single line of code.
Step 4: Define Your Schema
Structure the JSON output:
// Real example from Spark of Inquiry Deep Dive
const SparkQuestionsSchema = {
type: Type.OBJECT,
required: ["Interpretive Summary", "Follow-up Questions"],
properties: {
"Interpretive Summary": {
type: Type.STRING,
description: "4-5 sentence summary about their spark journey"
},
"Follow-up Questions": {
type: Type.OBJECT,
required: [
"The Origin Scene",
"The Emotional Core",
"The Naive Question",
"The Starting Assumption",
"The Personal Stake",
"The Disciplinary Bridge",
"Narrating the Spark"
],
properties: {
"The Origin Scene": { type: Type.STRING },
"The Emotional Core": { type: Type.STRING },
"The Naive Question": { type: Type.STRING },
"The Starting Assumption": { type: Type.STRING },
"The Personal Stake": { type: Type.STRING },
"The Disciplinary Bridge": { type: Type.STRING },
"Narrating the Spark": { type: Type.STRING }
}
}
}
};
The subcategory field enables fine-grained question organization:
- Each question gets tagged with its subcategory
- UI can group/randomize by subcategory
- Database queries can filter by specific question types
- Analytics can track which subcategories generate most engagement
Step 5: Clone and Adapt Handlers
Copy existing handlers:
cp generateSparkFollowupQuestions.ts generatePuzzlesFollowupQuestions.ts
cp saveSparkFollowupRound.ts savePuzzlesFollowupRound.ts
cp generateSparkSynthesis.ts generatePuzzlesSynthesis.ts
Update:
- Import your prompts
- Import your schema
- Change category strings
- Update API routes
That's it. The pattern handles the rest.
The Living Ledger: Why Everything Gets Saved
The handler trio pattern depends on the living ledger philosophy: everything is recorded.
What Gets Saved
Every AI-generated question:
{
session_id,
user_id,
category: "Puzzles and Unknowns",
subcategory: "The Unexpected Discovery",
question_text: "What surprised you most...",
model_used: "Google Gemini (latest available)",
created_at
}
Every student reflection:
{
session_id,
round_number,
answered_question_id,
user_reflection: "What surprised me most was...",
interpretive_summary: "AI's analysis...",
prompt_token_count: 1247,
response_token_count: 856,
thinking_token_count: 423,
total_token_count: 2526,
thought_summary: "AI's reasoning trace...",
model_used: "Google Gemini (latest available)"
}
Every synthesis:
{
session_id,
category: "Puzzles and Unknowns",
synthesis_text: "Your journey through puzzles...",
total_token_count: 3847,
thought_summary: "Pattern recognition...",
model_used: "Google Gemini (latest available)"
}
Why This Matters
For students:
- "Some" observability into AI operations
- Exportable record of their journey
- Evidence of intellectual growth
For instructors:
- Visibility into student thinking
- Assessment based on complete journey
- Early identification of struggles
For researchers:
- Complete audit trail of human-AI interaction
- Token analytics across different prompts
- Model comparison studies
For builders:
- Every interaction becomes data
- Pedagogy can be studied empirically
- System improvements are evidence-based
Technical Implementation Details
The Stack
Database: Supabase (PostgreSQL)
- Living ledger for all interactions
- Row Level Security for data boundaries
- Real-time capabilities (future feature)
AI Integration: Google Gemini (latest available) via @google/genai SDK
- Structured JSON output (
responseSchema) - Pure markdown synthesis (
text/plain) - Thought summaries (
thinkingConfig) - Complete token analytics
Backend: Next.js API Routes
- Handler trio pattern
- Session validation
- Metadata capture
Frontend: React + TypeScript
- Single-page components per category
- AI observability panels
- Export functionality
Security Through RLS
Row Level Security enforces data boundaries:
-- Students see only their own sessions
CREATE POLICY "Users can view own sessions"
ON rts_sessions FOR SELECT
USING (auth.uid() = user_id);
-- Instructors see all sessions in their courses
CREATE POLICY "Instructors can view course sessions"
ON rts_sessions FOR SELECT
USING (
course_id IN (
SELECT id FROM courses
WHERE instructor_id = auth.uid()
)
);
The Complete Flow
1. Student starts session → rts_sessions entry created
2. Generate handler → AI creates questions → logged to questions_log
3. Student selects question → UI captures choice
4. Student writes reflection → Save handler stores with metadata
5. Repeat steps 2-4 for rounds 1-4
6. Synthesize handler → AI reorganizes journey → stored to synthesis table
7. Student exports → Complete portfolio generated from ledger
Why This Matters for Humanities Faculty
You might be thinking: "I don't code. Why should I care about handlers and schemas?"
Here's why:
The handler trio pattern means that pedagogy is separated from code.
You can:
- Experiment with different prompts without touching code
- Design new analytical lenses as text instructions
- Test pedagogical approaches by updating prompts
- Share your teaching innovations as prompts, not code
The technical pattern is stable. The pedagogical creativity happens in the prompts.
This is what makes RTS extensible for educators, not just developers.
Real-World Example: From Spark to Puzzles
Let's trace how the same pattern creates two completely different experiences:
Spark of Inquiry Deep Dive
Pedagogy: Surface personal connection to research topic
Subcategories:
- The Origin Scene
- The Emotional Core
- The Naive Question
- The Starting Assumption
- The Personal Stake
- The Disciplinary Bridge
- Narrating the Spark
Prompt excerpt:
Help students reconstruct the vivid personal moment
their interest began. Questions should be:
- Specific to their lived experience
- Emotionally grounded
- Connected to course concepts
Result: Students explore WHY this topic matters to them personally
Puzzles and Unknowns Deep Dive
Pedagogy: Navigate research process and productive confusion
Subcategories:
- The Unexpected Discovery
- The Methodological Gap
- The Missing Voice
- The Productive Confusion
- The "I Used to Think..." Moment
- The Next Question
- Narrating the "How"
Prompt excerpt:
Help students surface contradictions and gaps in their research.
Questions should:
- Highlight methodological limitations
- Identify absent perspectives
- Validate productive uncertainty
Result: Students explore HOW their understanding evolved through research
Same handler trio. Completely different pedagogical work.
The “Philosopher-Builder” Insight
This pattern embodies a particular stance toward educational technology:
Not: "Here's a tool that does research"
But: "Here's a pattern for creating tools that scaffold thinking"
Not: "AI knows things"
But: "AI can generate questions about YOUR thinking"
Not: "One-size-fits-all pedagogy"
But: "Infinite pedagogical possibilities through prompt design"
The code implements constraints that enable creativity.
For Writing Instructors
Create movements around:
- Thesis development
- Argument structure
- Counter-argument exploration
- Revision strategies
- Audience awareness
For Science Educators
Create movements around:
- Hypothesis formation
- Experimental design
- Data interpretation
- Methodological reflection
- Public science communication
For Librarians
Create deep dives around:
- Search strategy development
- Source evaluation
- Citation practices
- Information ethics
- Research workflow
The pattern works for ANY domain where reflection scaffolds learning.
Next Steps: Building on This Foundation
If you want to extend RTS or build your own version:
- Study the pattern in movement1flow.md, database.md, and deepdive.md
- Understand the prompts - they contain the pedagogy
- Clone a working handler trio - don't start from scratch
- Experiment with prompts - test in Google's AI Studio before coding
- Define your schema - structure the questions you want
- Adapt the handlers - update category strings and routes
- Test end-to-end - verify the complete flow works
The pattern is proven. Your pedagogy makes it unique.
The Core Insight
Once you understand the handler trio pattern, you understand the entire RTS system.
- Movement 1 uses it
- All 6 Deep Dives use it
- Reflection Journal uses it
- BBMEs use it
- Future movements will use it
The pattern + prompts + schema + database = infinite extensibility.
This is what makes RTS shareable and repurposeable.
Further Reading
- Movement 1: Spark to Stakes — The original implementation explained
- Database & Living Ledger — Living ledger architecture in detail
- Deep Dive Flow — Complete implementation guide for extensions
- A Note on AI Use — AI integration philosophy
- AI Observability — Why observability matters pedagogically
The handler trio pattern is a way to turn pedagogical vision into extensible code.