Skip to content

StoryAdaptation Agent

Filelib/agents/adaptationAgent.ts (runAdaptationAgent)
Triggered byapp/api/adapt-story/route.ts (on Parent Insights → AI Story tab load)
Modelgemini-2.5-pro (Vertex AI)

What it does

Analyzes the child's last 5 quiz records from Firestore and generates a fully adapted next story configuration — a ready-to-use story prompt calibrated to the child's current reading level, interests, and performance trend.

Outputs:

  1. Adapted prompt — a 2–3 sentence story prompt that is ready to pass directly to StoryWriter, including vocabulary hints and pacing instructions
  2. Vocabulary levelsimple / moderate / rich matched to quiz performance
  3. Page count — 4 (struggling), 5 (average), or 6 (advanced)
  4. Focus theme — the central story theme
  5. Adaptation reason — one sentence explaining why this story was chosen (shown to parent)
  6. Encouragement — one warm sentence shown to the child before reading

Tools

ToolParameters
submit_adaptationadaptedPrompt, vocabularyLevel, pageCount, focusTheme, adaptationReason, encouragement

Vocabulary / difficulty logic

Average score (last 5 quizzes)Vocabulary levelPage count
< 50%"simple" — short sentences, common words4 pages
50–79%"moderate" — varied vocabulary5 pages
≥ 80%"rich" — complex sentences, richer vocabulary6 pages

The agent also considers the trend (improving / declining / stable) from the two most recent scores vs. the 5-quiz average.


Prompt crafting rules (agent instruction)

  • Look at what topics the child has already read — suggest something new but related
  • If they struggled, revisit the topic in a gentler, more engaging way
  • If they excelled, push them into a slightly more complex world
  • Include vocabulary hints in the prompt: e.g. "use simple 2-syllable words only" or "introduce 3 new interesting words with context"
  • Include pacing hints: "short punchy sentences" or "richer descriptive language"

Prompt structure:

[Story scenario]. [Vocabulary/pacing instruction for the AI]. [Emotional/learning goal].

Example — struggling reader:

"A little turtle named Pip finds a magical shell that grants one wish. Use simple short sentences and common everyday words. The story should focus on kindness and making good choices."

Example — advanced reader:

"A young inventor discovers an ancient library hidden beneath the ocean, filled with books written by sea creatures. Use rich descriptive language and introduce words like 'luminescent', 'labyrinth', and 'ancient'. The story should explore curiosity, problem-solving, and the power of knowledge."


Caching — avoid repeat Gemini calls

The adaptation result is cached in Firestore at users/{uid}/adaptations:

  1. API fetches the most recent cached adaptation and the most recent quiz date
  2. If latestQuizAt < cachedAt → returns cached result immediately (0 Gemini tokens)
  3. If a new quiz was completed since the last adaptation → runs the agent, saves the new result

Only the last 3 quizzed stories are used (not the full history) — this keeps the context small and generation fast (~2–4s vs. 10s+ with full history). The API route fetches the last 30 stories, filters to those with quiz scores, and passes the 3 most recent to the agent. The agent itself uses slice(0, 5) as a further safety cap.


Flow

Parent opens /parent-insights → AI Story tab


GET /api/adapt-story { userId, language }

    ├─ Fetch last 30 stories, filter to those with quiz scores → take 3 most recent

    ├─ Fetch most recent adaptation from users/{uid}/adaptations

    ├─ latestQuizAt < cachedAt?
    │       │ YES → return cached config  (0 Gemini tokens)
    │       │
    │       ▼ NO
    │   StoryAdaptationAgent LLM call
    │       └── submit_adaptation({
    │               adaptedPrompt: "...",
    │               vocabularyLevel: "moderate",
    │               pageCount: 5,
    │               focusTheme: "ocean adventure",
    │               adaptationReason: "...",
    │               encouragement: "..."
    │           })

    ├─ Save new adaptation to users/{uid}/adaptations


Parent Insights → AI Story tab shows AdaptedStoryCard:
    - Vocabulary level badge (Beginner / Intermediate / Advanced)
    - Page count pill
    - Focus theme pill
    - Encouragement quote
    - Full adapted story prompt
    - Adaptation reason (for parent)
    - "Start This Story" CTA → /create?prompt=<adaptedPrompt>

Input / Output

typescript
// Input (QuizRecord[] — last 5 only)
{
  storyTitle: string;
  storyPrompt: string;
  score: number;
  total: number;
  language: string;
  completedAt: string; // ISO string
}
[];

// Output (AdaptedStoryConfig)
{
  adaptedPrompt: string; // ready-to-use story prompt
  vocabularyLevel: "simple" | "moderate" | "rich";
  pageCount: number; // 4 | 5 | 6
  focusTheme: string; // e.g. "ocean adventure"
  adaptationReason: string; // shown to parent
  encouragement: string; // shown to child
}

ADK tool loop fix

The submit_adaptation FunctionTool execute returns { ...args, status: "submitted" } — the status field signals to the ADK runner that the tool has completed. Without this, Gemini re-invokes the tool in a loop (observed: 6+ calls before timeout). This fix applies to all three ADK agents: StoryAdaptationAgent, LearningAdvisor, and ParentInsights.


UI — AdaptedStoryCard

Rendered in components/dashboard/AdaptedStoryCard.tsx:

ElementContent
Header"Story Adapted for {childName}"
Subtitle"Based on N quizzes · AI personalized"
Encouragement boxconfig.encouragement
Vocabulary badgeBeginner / Intermediate / Advanced (color-coded)
Page count pillN pages
Theme pillconfig.focusTheme
Suggested story boxconfig.adaptedPrompt
Why this storyconfig.adaptationReason
CTA button"Start This Story" → /create?prompt=...
Refresh buttonRe-runs agent, saves new adaptation to Firestore

Observability span

Span name: agent/StoryAdaptationAgent

AttributeExample value
agent.nameStoryAdaptationAgent
agent.modelgemini-2.5-pro
agent.languageen
agent.avg_score76
agent.trendimproving
agent.vocabulary_levelmoderate
agent.page_count5
agent.duration_ms3400

Cost tracking

Logged to log/token-usage.jsonl in development:

json
{
  "sessionType": "learning_recommendation",
  "agent": "StoryAdaptationAgent",
  "model": "gemini-2.5-pro",
  "inputTokens": ~750,
  "outputTokens": ~200,
  "durationMs": 3400,
  "language": "en",
  "metadata": { "quizCount": 3, "vocabularyLevel": "moderate" }
}

Released under the MIT License.