story-tree

How We Put a Safety Classifier In Front of Every Story an AI Writes for a Child

Story Tree's AI Story App for Children

The Objective

Children want stories that belong to them, carrying their names and their ideas and read in a familiar voice. Parents want to control what gets generated and who in the household can use the app. A generic story library serves neither. We built an Expo mobile app over a .NET clean-architecture API, with the AI capability split into four independent Azure Functions microservices for generation, suggestions, cover art and voice-cloned narration, sitting behind a content-guardrail classifier and a per-service credit model.

Service Tags

Mobile App Development Microservices Architecture AI Integration Backend Development

Industry Tags

EdTech Children’s Apps Consumer AI

Tech Stack

Expo SDK 54 Bitbucket and Azure Pipelines Firebase MiniMax TTS Google Imagen Google Gemini via LangChain Azure Functions (Python 3) React 19 React Native 0.81 TypeScript Zustand TanStack Query MMKV ASP.NET Core .NET 8 PostgreSQL

The Impact

5
Violation types screened before generation
An input safety classifier runs ahead of every request rather than filtering the output afterwards.
4
Independent AI microservices
Generation, suggestions, thumbnails and narration each scale, fail and get billed on their own.
2
Isolated role sections with separate tokens
Parent and kid routing, sessions and notifications are separated at the token level.
1
Credit model covering every
AI call Per-service credit calculation with quota-exhaustion handling, so cost is bounded per account.
The Client

A Children’s App Where the Content Does Not Exist Until a Child Asks For It

Story Tree is a kids’ story app built on generation rather than on a library. A child contributes an idea, the app writes the story, illustrates it and narrates it in a cloned voice, and a parent sits above all of it with control over what happens and who can do it.

That inverts the usual children’s content problem. A publisher curates before a child ever sees anything. Here the content is created on request, which moves the entire safety question from editorial review to architecture.

The Problem

Generated Content for Children Cannot Be Moderated After the Fact

A story library can be reviewed. A generative app cannot, because the story does not exist until the moment a child is waiting to read it. Any safety model that depends on someone checking output after generation is a model that has already failed by the time it runs.

Parents and children also need genuinely different applications wearing the same skin. A parent manages the account, the subscription, the credits and what the child is allowed to do. A child needs none of that and must not reach any of it. Treating that as a permissions flag inside one session is how children end up in billing screens.

And four AI capabilities in one product is four independent cost centres, four independent latency profiles and four independent failure modes. Story generation, suggestions, cover art and voice cloning have almost nothing in common operationally, and running them as one service means the slowest and most expensive of them sets the behaviour of all four.

story-tree-app-screens
Our Approach

Safety Sits Ahead of the Model

The classifier went in ahead of generation rather than behind it. Screening what a child asks for is a tractable problem with a fast, cheap model. Screening what a large model produces is slower, more expensive, and arrives after the request has already cost money.

An Input Guardrail Across Five Violation Types

Every generation request passes a safety classifier covering five violation categories before any model is called. A blocked request never reaches generation, which means the guardrail also bounds cost rather than only bounding content.

Four Microservices Instead of One AI Layer

Generation, suggestions, thumbnails and voice run as independent Azure Functions in Python, communicating through Azure Storage Queue with Pydantic models on the boundaries. Voice cloning being slow does not make story suggestions slow, and an outage in image generation leaves stories readable without covers.

Separate Tokens for Parent and Kid

The two roles carry separate tokens with role-aware routing and notification isolation, rather than one session with a permissions check. A child’s session cannot reach parent surfaces because it does not hold a credential for them.

Two-Stage Generation With Word-Count Control

Stories generate in two stages with explicit length control, because a bedtime story that runs to three times the expected length is a usability failure even when the content is fine. Length is a product parameter here rather than a model outcome.

Credits Per Service

Each AI capability has its own credit cost and its own quota-exhaustion path. A voice-cloned narration and a story suggestion cost the business very different amounts, and a single undifferentiated credit would either overcharge for one or lose money on the other.

A Client That Survives a Bad Connection

Query cache and application state persist to MMKV, with infinite pagination over the story list. A child opening the app on a tablet with no signal still reaches the stories already generated rather than an empty screen.

What We Built

13 Modules Across a Mobile App, an API and Four AI Services

Each module maps to a role or a capability. The AI services sit behind the API rather than beside it.

Mobile app (iOS + Android)
Backend API
AI services
How It Was Built

A .NET Core With a Python AI Edge

The API is ASP.NET Core on .NET 8 in a clean architecture split across Core, Infrastructure and Host, with CQRS and FluentValidation. That is a deliberately conventional choice for the part of the system that holds accounts, credits, subscriptions and roles, because those are the components that must still behave predictably in three years.

The AI edge is Python, because that is where the model tooling lives. Four Azure Functions handle generation and suggestions through Gemini via LangChain, thumbnails through Imagen, and narration through MiniMax voice cloning, each with Pydantic models on its boundary so a schema change in one service cannot silently corrupt another.

The client is Expo SDK 54 with file-based routing, Zustand and TanStack Query persisted to MMKV, and an apisauce HTTP layer that injects the correct bearer token for whichever role is active. Role-aware token injection at the HTTP layer rather than in each screen is what keeps the parent and kid separation from depending on a developer remembering it.

CI/CD runs across Bitbucket and Azure Pipelines, matching the split between the application repositories and the Azure-hosted AI services.

Outcome

Stories a Child Asks For, Inside Limits a Parent Sets

A child contributes an idea and the app screens it, generates a story to a controlled length, produces a cover image, and narrates it in a cloned voice. A parent manages the account, the subscription, the credits and the child’s access from a section the child’s session cannot reach.

The four AI services scale and fail independently, and every call draws against a per-service credit balance with a defined exhaustion path, so the unit economics of a generated story are known rather than discovered.

The app is in build. This section describes the system as designed and needs rewriting with measured figures once it has launched.

Testimonials

"I came in wanting to talk about story quality. The first question back was what happens when a six-year-old types something they should not, and whether we would rather catch that before or after we had paid a model to write it. We had not thought about safety as a cost question at all."

Founder

Story Tree

Building a generative product for an audience where the content cannot be reviewed before it is seen?

We have built the guardrail layer before, and we know it belongs in front of the model rather than behind it.

Start the conversation