Sanity-check my LangGraph design before product demo

I am new to LangGraph and I have limited programing experience. I have a product demo on July 22, so I wanted to build a simple version of the app for demo purposes only. I’d like honest opinions on whether this architecture is the best fit or whether you’d build it differently.

App: a fitness-coaching tool. Core principle: deterministic Python rules make every
coaching decision (load progression, calories, rest days); the LLM only writes the
natural-language explanation, bound to a Pydantic schema at temperature 0; it never
touches a number the user sees.

Stack:

  • Orchestration: a single LangGraph 1.0 graph; intake → validate → load history →
    apply rules → save → format (LLM explains). No multi-agent.
  • Persistence: SqliteSaver checkpointer (session state) + a Store (cross-session
    profile) + my own SQLite tables (users, workout_history, audit_log). Local file,
    no server.
  • Frontend: Streamlit — one flow, graph compiled once, UUID4 thread_id per session.

Constraints: solo, limited experience, 3 weeks, small demo (not chasing scale).

Is this the best architecture for this, or would you approach it differently?
Please be blunt — I’d rather hear “you’re over-building X” now than after I build it.

Happy to share the repo if needed.

Your design looks right, one trim and one clarification.

Keep: A single linear LangGraph (not multi-agent). Python owns all coaching numbers; the LLM only explains a pre-computed plan at the end. Use SqliteSaver + thread_id for session state, and your own SQLite tables for users, workout history, and audit logs. Calling graph.invoke() directly from Streamlit is fine for a demo.

Trim: Skip the LangGraph Store for now. Use SqliteSaver for thread state and your SQLite tables for domain data (including cross-session profiles). That’s two persistence layers, not three.

Use structured output in the final format node, model.with_structured_output(PydanticSchema).

Bottom line: Custom LangGraph + deterministic rule nodes + one LLM call at the end is the right initial pattern for a fitness coach where correctness matters. Good luck with the demo.

Optional experiment (not for v1): Once the demo works, you could try a create_agent orchestrator with tools for loading context, strict system prompt, tools that fetch data but never compute coaching numbers. Stronger models (e.g. Opus, GPT-5.x) follow instructions better, but you’re trading explicit control for convenience. I wouldn’t start there given your timeline; prove the deterministic pipeline first.

After v1, you can gradually move right on the workflow → agent spectrum (see diagram): more flexible intake and context-loading, while keeping the rules engine deterministic. The goal isn’t to let the model decide loads or calories, it’s to reduce boilerplate on orchestration as you learn what actually needs to be dynamic.

This is surface-level advice based on what you’ve shared. If you post the repo, I’m happy to give more concrete feedback.

Thanks, this is helpful. Current v1 build is aligned with that trim: two persistence layers only. SqliteSaver owns live thread/session state, and the SQLite domain tables own durable data: users, workout_history, audit_log. Store is deferred/stubbed, not a second profile copy.

I also added the merge-point contract from the other feedback. Both paths, new-user build-plan and returning-user apply-rules, must conform to the same Save input schema before anything is persisted or sent to Format/LLM.

I attached the current schema/flow. Does this look like the right boundary, or would you move the contract somewhere else?

I hope this will be good enough for a Demo and my first ever langgraph build

Yes: Save is the right boundary for the shared Pydantic contract. Both branches should produce the same shape before anything is persisted or sent to Format/LLM, and gating at the merge point is the right call.

The diagram looks solid for a demo: error path fails closed (no LLM), Format before LLM-explain is the right split, and the two decision points are exactly what a graph is for. More than good enough for a first LangGraph build, ship it.

Post-v1: You could break apply rules - adjust into smaller units (build_workout_plan, adjust_load, etc.). If the path stays fixed, prefer deterministic graph nodes; if you want more flexibility, wrap the same Python as @tools in a create_agent orchestrator. Either way, rules stay deterministic, the LLM still only explains what Save already locked in.