Skip to main content

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.

GenerateSaveSynthesize

This could be viewed as the computational expression of a pedagogical philosophy:

  1. Generate questions based on student thinking (not AI knowledge)
  2. Save the journey with complete observability
  3. 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?

  1. Write a pedagogical prompt
  2. Define a JSON schema for questions
  3. Clone the handler trio
  4. Update the prompt and schema
  5. 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:

  1. Spark of Inquiry - Personal connection lens
  2. Inquiry as Story - Narrative structure lens
  3. Stakes and Significance - Public relevance lens
  4. Puzzles and Unknowns - Research process lens
  5. Listeners and Lens - Audience awareness lens
  6. 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:

  1. The Unexpected Discovery
  2. The Methodological Gap
  3. The Missing Voice
  4. The Productive Confusion
  5. The "I Used to Think..." Moment
  6. The Next Question
  7. 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:

  1. Study the pattern in movement1flow.md, database.md, and deepdive.md
  2. Understand the prompts - they contain the pedagogy
  3. Clone a working handler trio - don't start from scratch
  4. Experiment with prompts - test in Google's AI Studio before coding
  5. Define your schema - structure the questions you want
  6. Adapt the handlers - update category strings and routes
  7. 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


The handler trio pattern is a way to turn pedagogical vision into extensible code.