Steady State · Case study

Coach AI assistant

Ask in plain English, approve before anything changes

A coach types a normal sentence into the dashboard, like "Who is at risk this week?" The assistant answers from the data the app already has. When a request would change something, such as sending a message, it shows a preview first and waits for the coach to press Confirm.

  • Team of 3
  • 8 tickets
  • 5 tools
  • 2 requests
  • 0 new database tables
Ask AnythingTry it

Send Tharani a note saying good job on yesterday's session

Message to TharaniPreview

Good job on yesterday's session!

In the app, nothing is sent until the coach presses Confirm. This demo sends nothing.

01The parts

What the system is made of

Three places share the work: the screen the coach uses, the server, and a few helpers the server calls on. Each part is listed with the name it has in the code.

On the screen

  • Chat window

    Ask Anything bar

    Where the coach types, and where answers and preview cards show up. It is a React component in the dashboard.

  • Screen state

    Zustand store

    Remembers the chat so far, whether the panel is open, and whether a preview is waiting for approval.

On the server

  • Request

    GraphQL mutation

    A structured message from the screen to the server. There are two, sendAssistantMessage and confirmAssistantAction. GraphQL calls any request that does something a mutation, so both count, even when the answer is only a lookup.

  • Orchestrator

    CoachAssistantService

    A NestJS service in coach-assistant.service. It decides which step runs next and in what order. It does not read English.

  • Pending action store

    expires after 10 min

    Holds a preview between the first request and the confirmation. It lives in server memory for now, so a restart clears it.

Helpers the server calls

  • Intent classifier

    OpenAI function calling

    Our code sends OpenAI the coach's sentence and a list of the five tools. OpenAI replies with which tool to use and what to give it. Everything after that is ordinary code.

  • Read tools (3)

    stats, at risk, details

    Look up athlete stats, who is at risk, and full athlete details. They change nothing.

  • Write tools (2)

    draft message, change schedule

    Build a preview of a change. They only act after the coach confirms.

  • Existing services

    athletes, messages, workouts

    What the app already had. The assistant calls these instead of storing anything new.

02Walkthrough

Follow a request

Pick a request, then step through it. Filled boxes are working at the current step, and outlined boxes already did their part.

The coach types: Who is at risk this week?

Step 1 of 6

The coach types a sentence

The coach clicks the Ask Anything bar in the top right corner and types the way they would text a person. There are no menus or special commands.

Called at this step

  • ScreenThe coachtypes in plain English
  • ScreenChat windowAsk Anything bar

Passed along

"Who is at risk this week?"

On the screen

  • The coachtypes in plain English, working at this step
  • Chat windowAsk Anything bar, working at this step
  • Screen stateZustand store

On the server

  • RequestGraphQL mutation
  • OrchestratorCoachAssistantService
  • Pending action storeexpires after 10 min

Helpers the server calls

  • Intent classifierOpenAI function calling
  • Read tools (3)stats, at risk, details
  • Write tools (2)draft message, change schedule
  • Existing servicesathletes, messages, workouts

03Decisions

Four choices that shaped it

Ask before acting

Reading is harmless, so answers appear right away. Writing changes real data, so the assistant shows a preview and waits for Confirm. The write only runs on a second request. Across six weeks of live use at Steady State, none of the AI-proposed writes was incorrect.

Let the model choose and let normal code do the work

We never taught the server to read English. We gave OpenAI a list of five tools and it picks one. Our own code then runs that tool, and it behaves the same way every time.

Ask instead of guessing

When a name matches several athletes, the assistant stops and asks which one. Messaging the wrong person costs more than one extra click.

Reuse what exists

The assistant is a new way into services the app already had: stats, messages, and workouts. We added no database tables and needed no migration, which kept the change small and easy to review.

04Delivery

Eight tickets, two tracks

We split the work into eight tickets under one epic, which is a group of related tickets. Each ticket became a pull request that teammates reviewed before it merged.

Server track

  • Scaffold

    The empty module, the two requests, and an intent classifier with nothing behind it yet.

  • Read tools

    Three tools that look up athlete stats, who is at risk, and full athlete details.

  • Write tools and the pending action store

    Draft a message, change a schedule, and park the preview until the coach confirms.

  • Fuzzy names and multi-step requests

    Loose name matching, the "which one?" reply, and running several tools in order.

Screen track

  • The shell

    The Ask Anything bar, the chat window, and the Zustand store behind them.

  • Read wire-up

    Connected the shell to the server so questions get real answers.

  • The confirm card

    The preview card with Confirm and Cancel, and the request that sends the approval.

  • Polish

    A rate limit, accessibility work, keyboard shortcuts, and an error screen.