Conversation Flow

Standard companion chat prepares a child-specific turn, sends it through the Gemini conversation generator, and returns a parsed reply with emotion and a robot suggestion.

[!NOTE] The source defines this flow, but does not verify that a deployed chat request reached Gemini or received a successful response.

flowchart TD
    post["Chat POST handler"] --> checks["Authenticate; validate child and message; look up owned child"]
    checks --> access["Check voice-AI consent, conditional age policy, and API key"]
    access --> gate{"Access allowed?"}
    gate -- "No" --> denied["Return access response; stop before session processing"]
    gate -- "Yes" --> session["Validate or create session"]
    session --> context["Assemble instructions and history"]
    context --> ready{"Gemini ready?"}
    ready -- "No" --> failure["Return failed turn"]
    ready -- "Yes" --> models["Request with selected model"]
    models --> turn["Gemini turn result"]
    turn -- "Failed" --> failure
    turn -- "Successful" --> persist["Batch transcript and session update"]
    persist --> response["Return reply and suggestions"]

Gemini provider selection

The handler calls generateGeminiConversationTurn in app/api/_lib/gemini-conversation.ts with assembled instructions, history, child input, maxReply set to 1600, and maxOutputTokens set to 4096. The generator first checks readiness, then builds a deduplicated model list from GEMINI_CHAT_MODEL, gemini-3.6-flash, and gemini-flash-latest. It tries another model only when the result reason is model_not_found; other failures return immediately, and exhausting the list returns the last failure.

[!IMPORTANT] The access gate requires voice-AI consent, distinct from the long-term-memory consent used for the memory input below. It requires child-data-policy confirmation only when the child’s age is unknown or under 13. The generator’s readiness check always requires both GEMINI_API_KEY and GEMINI_CHILD_DATA_POLICY_CONFIRMED. So a child aged 13 or older can pass the access gate while the generator returns not_configured; check the server-side child-data-policy setting if that occurs.

Context assembled for the turn

The handler combines profile, memory, history, locale, and personality inputs into the instructions and conversation context passed to Gemini.

[
    {
        "title": "Profile",
        "body": "When a profile exists, its context includes preferred name (falling back to the child's name), age, dialect, communication style, attention span, interests, favorites, learning goals, and routines. Without a profile, the handler uses age and preferred language."
    },
    {
        "title": "Approved memories",
        "body": "The handler queries memories only when long-term-memory consent is present. It limits them to approved, non-sensitive, unexpired records, ordered by most recently updated, with a maximum of 20. If none are supplied, the instructions say there are no approved long-term memories."
    },
    {
        "title": "Conversation history",
        "body": "The handler retrieves up to 12 messages for the session in descending creation order, reverses them, and maps Mogo messages to assistant turns and other messages to user turns. The Gemini generator takes only the last 10 history entries, skips entries whose trimmed content is empty, then appends the current input."
    },
    {
        "title": "Locale and personality",
        "body": "The parser accepts mischievous and maps every other personality value to nice. The handler passes the parsed personality and locale to personalityInstructions, which selects localized safety, tone, language, and robot instructions."
    }
]

Gemini outcomes

A failed generator result takes the handler’s error branch, which returns a 502 using the provider reason or assistant_failed.

Generator outcome Handler result
Readiness check fails not_configured; no model request
Provider request throws network_error
Provider returns a non-success response A reason mapped from the response status and diagnostic
Safety block is reported safety_blocked
Parsed reply is empty empty_response
Parsed turn succeeds Return a successful response

parseConversationTurn in app/api/_lib/mogo-conversation.ts treats non-JSON text as a reply truncated to the configured maximum. For JSON, it trims a string reply or falls back to the raw text when reply is not a string; an empty reply is rejected.

[!WARNING] Treat the returned robot value as a suggestion, not a completed robot action. The chat response does not establish that any downstream consumer acted on it or that a physical effect occurred.