Skip to content

← Work

Playbook AI Workout Builder

Playbook Coaching sells tailored multi-week training programs. Before this tool, staff built those programs mostly by hand — in Excel, or by writing the JSON directly — with almost no software helping them. I led development of an internal builder that generates a structured program, lets staff refine and edit it, keeps a human in the review seat, and exports straight into Playbook's existing platform schema.

At a glance

  • Role: Technical lead and core builder across the generation pipeline, domain model, conversational refinement, editor, review, and export.
  • Stage: Shipped and used internally; staff reported that the generate-refine-export workflow helped, but time saved and error reduction were not measured.
  • Core constraint: Turn probabilistic model output into editable, human-reviewed data that matches an existing platform schema.
  • Strongest evidence: Day-by-day LangGraph generation, Zod validation at every boundary, deterministic editing, and schema-compatible export.

Context

A training program is a nested structure: weeks contain days, days contain circuits, circuits contain exercises with their own sets, reps, and loading. Authoring that by hand is slow, and hand-writing it as JSON means a coach is doing data entry in a format built for machines, where a misplaced bracket is indistinguishable from a programming decision.

The constraint that mattered most was on the other end. Playbook already had a platform, and programs had to land in it. A tool that produced beautiful output in its own format would have just moved the manual work downstream into re-entry.

So the problem was never only generation. It was getting from a coach's intent to a valid document in someone else's schema, with a human able to intervene anywhere along the way.

My role

I led development and built the core architecture and product: the generation pipeline, the domain model, conversational refinement, the structured editor, the review step, and the export transform. I owned the stack and engineering direction.

Product and visual design came to me as Figma. Supporting developers built bounded later features under my direction, including transcription and file and audio handling, and some authentication and circuit editing work.

Architecture

The application is Next.js and React, with editor state in Zustand and server state in TanStack Query. Firebase holds chats, messages, and stored workout documents. Generation runs through a LangGraph workflow against Gemini, with GPT-4o also in use.

Public-safe Playbook workflow showing structured intake, day-by-day generation, schema validation, conversational editing, human review, and export.

The domain model comes first. Weeks, days, circuits, and exercises are defined as schemas before anything else, and Zod validates every path into them. Generation, chat refinement, manual editing, and export are all expressed against that one model rather than each carrying its own idea of what a program is. That is what makes it possible to accept probabilistic input at one end and emit a deterministic document at the other.

Stack

Application — Next.js 15 · React 19 · TypeScript · Zustand · TanStack Query and Table · dnd-kit

AI — LangGraph · LangChain · Gemini · GPT-4o

Data and delivery — Zod · Firebase · Firestore · Vercel

Engineering challenges

Generation that got less reliable as programs got bigger

The problem. The first implementation asked the model for an entire multi-week program in a single request. It was slow, and it frequently came back with invalid JSON, which broke parsing and left the editor with nothing to work on.

Why it was hard. This was not a prompt to be tuned. The amount of structure demanded of one response grew with the length of the program, so the tool became least reliable exactly when it was asked to do the most useful work. Retrying was expensive and all-or-nothing: one malformed exercise on day nineteen threw away the whole generation, including the eighteen days that were fine.

The decision. Stop asking for the program. Ask for a day, and make each day independently valid.

Implementation. I rebuilt the generator as a LangGraph workflow that produces one constrained day at a time. Graph state carries the relevant previous-day and previous-week context forward, so later days are still coherent with what came before rather than generated in isolation. Each day is validated against the Zod workout schemas as it arrives, the workflow advances through days and weeks while reporting progress to the UI, and the finished structure is persisted for editing and export.

Outcome. A single oversized JSON response stopped being the only route to a working program. Each step carries a fraction of the structural burden and is checked on its own, and staff watch a program build up instead of waiting on one opaque request.

Conversational output that had to become an editable program

The problem. Staff refine programs by talking about them, and the model answers in prose and markdown tables. The editor, the preview, and the export need exact structure. Those two things do not meet on their own.

Why it was hard. Model output is shaped for a human reader and is only approximately consistent. Downstream, none of that is acceptable: circuit operations, drag-and-drop reordering, and the export transform all need to know precisely what they are manipulating. Letting roughly-correct text through would put an ambiguous program on the path to a real client.

The decision. Treat the schema as a boundary rather than a formality. Everything probabilistic happens on one side of it, and nothing crosses without validating.

Implementation. Conversational responses are generated as markdown tables, then run through an extraction, repair, and validation path into the domain model. Recoverable malformations get repaired rather than discarded, and what fails validation does not become a program. Past that boundary the workspace is fully deterministic: circuit editing, combining and breaking circuits, rearranging, and drag-and-drop ordering all operate on validated structure.

Outcome. Chat is a way to steer the program rather than a way to produce it, and every operation after the boundary behaves predictably.

Fitting output into a system that already existed

The problem. A generated program only has value if it can be uploaded into Playbook's existing platform, which has its own schema and was not designed for authoring.

Why it was hard. The two representations want different things. An authoring model needs to be nested, mutable, and convenient for circuit operations and partial regeneration. The platform's schema needs to be exactly what it is. Authoring directly in the target format would have made editing painful; ignoring the target format would have pushed the work back onto staff.

The decision. Keep both, and put a deterministic transform and a human between them.

Implementation. Staff work in the internal model throughout generation, refinement, and editing. Review is an explicit step, not an implied one — nothing is treated as final because a model produced it. Approved programs are then transformed into the JSON the existing platform expects and uploaded there.

Outcome. This is the part staff singled out. The workflow helped because the output fit the system they already used, so generating a program did not create a second job of re-entering it.

Creator onboarding

The related onboarding application collects a complete creator packet before internal delivery work starts: an eight-step intake covering identity, program details, brand brief, brand board, files, and revision-policy acknowledgment. Long forms lose work, so draft state is validated with Zod and persisted through Zustand, and file uploads are resumable with progress feedback into Firebase Storage. Submissions land in Firestore for internal review, notify the team in Slack, and hand off to a Calendly booking for branding review.

One detail worth keeping: submissions were failing because upload metadata still carried the browser's own file objects, which Firestore cannot serialize. The fix was to strip the non-serializable blobs while preserving the metadata and download URLs that the review flow actually needs.

Outcome

The builder shipped and was used internally to produce programs for real clients. Staff reported that the generate, refine, and export workflow helped substantially, specifically because its output fit their existing platform.

The design principle that made it usable is where the probabilistic part stops. Generation and chat propose; schemas validate; people decide; export is deterministic. Human review is a feature of the product rather than a caveat attached to it.