Lucas
Highlight Tiny Titan eligibility
ba3384e
|
Raw
History Blame Contribute Delete
12.8 kB

A newer version of the Gradio SDK is available: 6.20.0

Upgrade
metadata
title: Tiny Strength Coach
emoji: 🐨
colorFrom: blue
colorTo: yellow
sdk: gradio
sdk_version: 6.17.3
python_version: '3.12'
app_file: app.py
pinned: false
license: apache-2.0
short_description: Build Small Hackathon - tiny strength-training coach
preload_from_hub:
  - unsloth/Qwen3-1.7B-GGUF Qwen3-1.7B-Q4_K_M.gguf
tags:
  - track:backyard
  - achievement:offgrid
  - achievement:llama
  - achievement:sharing
  - achievement:fieldnotes
  - tiny-titan

Tiny Strength Coach

A one-user strength-training coach built for the Hugging Face Build Small Hackathon Backyard AI track.

I use it for my own hypertrophy training loop: write a messy daily check-in, let a tiny model parse it into structured fields, build a deterministic workout from my training rules, train, log the sets, and let the next session use that history.

The key design split:

  • Tiny parser: Qwen3 1.7B GGUF via llama.cpp turns free text into structured fields.
  • Deterministic engine: plain Python applies the training rules. No LLM calls happen inside the engine, and the model never decides training numbers.

Submission Links

Try the Demo

Paste this check-in into the app:

I only have 30 minutes, slept 5 hours, energy is low, and my right tricep hurts.

Expected behavior:

  1. The tiny model extracts time, sleep, energy, and tricep pain.
  2. The structured fields stay visible and editable.
  3. The deterministic engine removes triceps-involving work, compresses the session for the short time window, adjusts target RIR for readiness, and shows the reasoning in plan notes.
  4. The logging area lets me save completed sets so the next session can progress from history.

Latency note: the public Space runs a local GGUF model on free CPU hardware. The first parse can take about 60-90 seconds, and follow-up parses can still take tens of seconds. This is slow, but intentional for the hackathon story: no cloud inference API is used; the tiny model runs inside the Space, and the workout engine runs instantly after parsing.

Hackathon Fit

  • Backyard AI: this solves my own real training problem, with my real daily loop and training-history feedback.
  • Small model constraint: Qwen3 1.7B is far below the 32B parameter cap.
  • Tiny Titan fit: the app's only LLM is Qwen3 1.7B, a genuinely tiny model under the special award's suggested 4B scale.
  • Local-first / no cloud inference: local development uses Ollama; the Space uses llama.cpp with a GGUF model loaded in the app process.
  • Inspectable logic: the LLM only parses language. Training choices come from deterministic, tested Python rules.
  • Actual use: completed-session logs drive day rotation and double progression.

Badge / Award Targets

Target Evidence
Field Notes Published write-up linked above.
Llama Champion Space runtime uses llama.cpp with a Qwen3 GGUF model.
Off the Grid / local-first No external inference API; JSON storage works locally by default.
Tiny Titan special award The app's parser is Qwen3 1.7B, and no larger hidden inference API does the work.
Best Demo Short YouTube demo linked above.

Not currently targeted: fine-tuning / Well-Tuned, custom gr.Server UI / Off-Brand, or agent-trace sharing.

Run Locally

.venv/bin/python app.py

Then open:

http://127.0.0.1:7860

Run tests:

.venv/bin/python -m unittest discover -s tests

Reset the local Gradio server:

scripts/reset_server.sh

The reset script stops whatever process is listening on port 7860 and then starts .venv/bin/python app.py. You can override defaults with PORT=... or APP_COMMAND=....

Daily Flow

  1. Send a natural-language check-in in the conversation.
  2. Answer any follow-up questions in the same conversation.
  3. Review or edit the structured fields.
  4. Build today's session.
  5. Train and fill performed-set inputs.
  6. Save the completed session.
  7. The next session uses the saved history for day rotation and progression.

Local history is stored in:

data/completed_sessions.json

This file is ignored by git.

Persistence Modes

The app is local-first by default. With no environment variables, it uses local JSON and has no cloud storage dependency.

For deployed Hugging Face Spaces, set these variables to use a private Hugging Face Dataset as durable history storage:

HF_HISTORY_DATASET_REPO=your-username/training-coach-history
HF_TOKEN=hf_...
HF_HISTORY_FILE=completed_sessions.json

Optional:

HISTORY_STORE=hf_dataset
HF_HISTORY_PRIVATE=1
LOCAL_HISTORY_PATH=data/completed_sessions.json

HF_TOKEN should be added as a Space secret, not committed to the repo.

Check live HF storage without uploading:

HF_HISTORY_DATASET_REPO=your-username/training-coach-history \
HF_TOKEN=your-token \
.venv/bin/python scripts/check_hf_storage.py

Hackathon badge compromise: the app can run fully locally with JSON storage and no cloud dependency. The Hugging Face Dataset backend is optional deployment persistence, not cloud model inference or training logic.

Parser Runtime Modes

Copy .env.example to .env for local settings. .env is ignored by git and is loaded automatically by app.py before storage or parser backends are configured.

Local development defaults to Ollama:

PARSER_BACKEND=ollama
OLLAMA_MODEL=qwen3:1.7B

Hugging Face Spaces should use the GGUF llama.cpp runtime. Set the thread count to the actual Space hardware quota: for example, 2 on free CPU Basic or 8 on cpu-upgrade.

PARSER_BACKEND=llama_cpp
LLAMA_CPP_MODEL_REPO=unsloth/Qwen3-1.7B-GGUF
LLAMA_CPP_MODEL_FILE=Qwen3-1.7B-Q4_K_M.gguf
LLAMA_CPP_MAX_TOKENS=512
LLAMA_CPP_N_CTX=2048
LLAMA_CPP_N_THREADS=2

The Space README frontmatter preloads only the selected GGUF file at build time so the app does not need to download model weights during the first parser request. Set LLAMA_CPP_N_THREADS to the Space CPU quota, not the host core count. Prefill threads default to the same value because llama.cpp would otherwise spawn one thread per host core, which can collapse under the container's CPU quota; override with LLAMA_CPP_N_THREADS_BATCH if needed. On startup the app warms the model in a background thread so the constant prompt prefix is already in the KV cache before the first user request.

The llama.cpp backend constrains output with a small generic minified-JSON grammar instead of a full Pydantic-schema grammar: the generated schema grammar made per-token sampling unusably slow on Space CPUs. The prompt sends a compact response shape rather than the full JSON schema, and the ChatML prompt prefills an empty Qwen3 <think> block so JSON-constrained generation stays on-distribution. Pydantic validation plus deterministic repair in parse_model_response remains the schema contract before the engine sees any data.

If PARSER_BACKEND is not set, the app chooses llama_cpp when the Hugging Face SPACE_ID environment variable exists, otherwise ollama.

The old Transformers parser backend still exists for explicit local experiments, but it is not installed by the default deployment requirements and is no longer the Space runtime path.

Rule Source

The canonical training-rule source is:

hypertrophy_app_training_rules.md

DECISIONS.md records which rules have been accepted for the MVP. PLAN.md tracks what is done and what is next. MVP_COMPROMISES.md tracks deliberate shortcuts.

Implemented Rules

Rules run in this order:

completed history
-> next training day
-> fixed 4-day template
-> double progression
-> pain filtering
-> time compression
-> readiness adaptation

1. Next Training Day

The app looks at the last completed session and rotates:

Day 1 -> Day 2 -> Day 3 -> Day 4 -> Day 1

If there is no history, it starts at Day 1.

2. Fixed 4-Day Template

The MVP uses the fixed four-day exercise plan defined in training_coach/engine.py. All rests are normalized to 1 minute for now.

Effect on the plan:

  • selects today's exercise list
  • preserves exercise order
  • creates grouped exercises with prescribed straight sets

3. Double Progression

Single-rep template prescriptions become a 5-rep progression range with the supplied reps as the middle target. For example, 13 reps becomes target 13 inside range 11-15.

Rule: if the latest matching exercise exposure hit the top of the rep range on every set, the next target load increases by +1 kg and target reps reset to the low end of the range.

If not all sets hit the top of the range, the app repeats the latest load and raises the next target by up to +2 reps per set.

Effect on the plan:

  • fills recommended target loads
  • fills recommended target reps inside the progression range
  • adds notes explaining whether to add reps or add load

MVP compromise: all exercises use the same +1 kg increment.

4. Pain Filtering

Rule: if pain_or_injury is yes and a PainIssue has a known affected muscle, the engine removes exercises tagged with that muscle.

This includes secondary involvement. For example, triceps pain can remove pressing and triceps-involving work.

Effect on the plan:

  • removes risky exercises
  • reorders remaining exercises compactly
  • adds a plan note explaining what was removed

MVP compromise: it removes exercises but does not choose replacements yet.

5. Time Compression

Time compression runs after pain filtering.

Current MVP bands:

Available time Effect
60+ min full remaining plan
45-59 min about 75% of sets
30-44 min about 60% of sets
<30 min first four exercises only, capped at 8 sets

Effect on the plan:

  • reduces set count
  • cuts later/lower-priority sets first
  • preserves exercise order
  • adds a plan note explaining the set reduction

MVP compromise: it does not estimate warm-up time, transition cost, or true exercise duration.

6. Readiness Adaptation

Readiness is computed from structured check-in fields:

  • sleep quality
  • sleep duration
  • energy
  • soreness text
  • mood/stress

Current MVP behavior:

Readiness Effect
very low about 50% fewer sets, target RIR 4
low about 20% fewer sets, target RIR 3
normal unchanged sets, target RIR 2
high unchanged sets for MVP, target RIR 2

Sleep under 5 hours applies a stress cap: minimum target RIR 3.

Effect on the plan:

  • reduces sets on low/very low readiness days
  • fills target RIR
  • adds a readiness note with the score and action

MVP compromise: soreness is inferred from text, and stress/mood share one field.

Parser Behavior

The parser extracts what it can and proposes structured follow-up items when useful. Each follow-up has a fixed field, a question, and a reason. The app then derives the display questions from those structured items.

Deterministic cleanup still handles known safety and quality issues, such as:

  • avoiding repeated sleep questions
  • removing model-invented activity follow-ups when no activity was mentioned
  • treating meh, fine, or okay sleep as sleep_quality="okay"
  • mapping body-part terms like tricep to anatomical muscles

The parser does not make training decisions. It only creates structured inputs for the engine.

Useful Test Check-Ins

Normal day:

I feel okay, slept fine for 7 hours, and have 60 minutes.

Short day:

I only have 35 minutes today, slept okay, medium energy.

Low readiness:

I slept 5.5 hours, energy is low, I feel neutral, and I have 60 minutes.

Very low readiness:

I slept 4.5 hours, energy is low, I am stressed, and I feel very sore.

Pain filtering:

I have 60 minutes, sleep was okay, but my right tricep hurts.

Combined stress test:

I only have 30 minutes, slept 5 hours, energy is low, and my right tricep hurts.

Current MVP Limits

  • One user only.
  • Fixed 4-day template.
  • Straight sets only.
  • Local JSON by default; optional Hugging Face Dataset storage for deployed history.
  • Pain removes exercises but does not substitute replacements.
  • No mesocycles, deloads, volume landmarks, or periodization yet.