Skip to content
· 4 min readTutorialsExpoAI
Building an AI Coaching App in Expo with Local LLMs

Building an AI Coaching App in Expo with Local LLMs

Malik Chohra
Malik Chohra
Creator of WireAI

The technical blueprint for building a privacy-first AI coaching app using the WireAI SDK, Ollama, and native generative UI components.

Building an AI coaching app in Expo requires structured data collection, not just text bubbles. By using the WireAI SDK and a local model like Llama 3, you can generate native mood selectors and habit trackers dynamically, ensuring user privacy and a premium mobile experience.

A mental wellness app shouldn't feel like a terminal window. It's a clear use case for generative UI. You want the AI to present structured questions, log tappable responses, and summarize progress natively.

What is the ideal app flow?

Instead of a blank text input, let the app guide the user. The WireAI agent can render a `MoodSelectorCard` to start the session, follow up with a text input for journaling, and conclude with a `ProgressSummaryCard`.

How do you register custom coaching components?

Define your component and register its schema with WireAI.

import { registerComponent } from 'wireai-rn';
import { z } from 'zod';

export const MoodCheckIn = registerComponent({
  name: "MoodCheckInCard",
  description: "Use at the start of a coaching session to ask the user their mood.",
  schema: z.object({
    options: z.array(z.string()).describe("List of 4 mood options"),
  }),
  render: ({ props }) => <MoodUI options={props.options} />
});

How do you tune the system prompt?

WireAI automatically handles the JSON formatting instructions, the registry listing, and the one-component-per-turn constraint. You only supply the domain context through systemPromptSuffix. For a coaching app, the suffix carries the persona, the safety boundaries, and the conversational pacing:

const { messages, sendMessage } = useWireAIThread({
  systemPromptSuffix: `
You are an empathetic wellness coach. Rules:
- Ask exactly one question per turn using a UI component.
- Open every new session with MoodCheckInCard.
- Never give long paragraphs of advice. Reflect, then ask.
- If the user mentions self-harm or crisis, render
  CrisisResourceCard immediately and stop the flow.
`,
});

The safety branch matters. A coaching agent will eventually meet a user in crisis, and a text-only bot tends to ramble at exactly the wrong moment. By giving the model a dedicated CrisisResourceCard component and an explicit instruction to render it, you turn a judgment call into a deterministic UI outcome: the agent picks the card, Zod validates the helpline props, and the native component shows the right resources. The model never has to free-form a crisis response.

How do you keep the session private and on-device?

The reason to build a coaching app on local LLMs is that the conversation never leaves the phone. Point the OllamaAdapter at a local runtime and no journal entry, mood log, or message is sent to a cloud provider. That is the difference between a wellness app you can put in front of a privacy review and one you cannot.

import { WireAIProvider } from 'wireai-rn';
import { defaultComponents } from 'wireai-rn/components';

export default function App() {
  return (
    <WireAIProvider
      llm={{
        provider: 'ollama',
        baseUrl: 'http://localhost:11434', // 10.0.2.2 on Android emulator
        model: 'llama3:8b-instruct-q4_K_M',
      }}
      components={[...defaultComponents, MoodCheckIn]}
    >
      <CoachingScreen />
    </WireAIProvider>
  );
}

For setup details, including the localhost-versus-LAN-IP trap that breaks most first attempts, see the Ollama and React Native setup guide. On-device persistence pairs naturally here: store the thread with MMKV so a returning user's history stays local too.

How do you summarize progress at the end of a session?

A coaching session should close with a recap, not just trail off. Register a ProgressSummaryCard and instruct the model to render it as the final turn. Because the whole conversation lives in the thread context that WireAI manages, the model has the full session to summarize from. You are not stitching together a separate summarization call, the same agent that ran the session writes the recap into a validated component.

export const ProgressSummary = registerComponent({
  name: "ProgressSummaryCard",
  description: "Render at the end of a session to recap mood, themes, and one next step.",
  schema: z.object({
    moodTrend: z.string(),
    themes: z.array(z.string()).max(3),
    nextStep: z.string(),
  }),
  render: ({ props }) => <SummaryUI {...props} />,
});

This is the broader argument for generative UI in a wellness product: the structured cards are what make the experience feel like a designed app rather than a chat window, and the local model is what makes it private. The agent decides which card fits the moment, WireAI guarantees the props are valid before anything renders, and the user taps through native components the whole way.

What models handle this reliably?

Coaching prompts are structured-output tasks, not prose generation: pick one component, fill its props, return valid JSON. Instruction-tuned local models do this well. llama3:8b-instruct-q4_K_M is a solid default on an M1-class machine; phi3:mini is faster when latency matters more than nuance. Avoid base (non-instruct) models, which hallucinate component names and malform JSON far more often. The full reliability comparison is in the Llama 3 on React Native guide.


Build better mobile workflows. Run npm install wireai-rn zod.