Files
Training-Bot/docs/superpowers/specs/2026-05-20-training-assistant-design.md

236 lines
8.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Training Assistant — Design Spec
**Date:** 2026-05-20
**Status:** Approved
---
## Overview
A personal Telegram bot that tracks anal training sessions, sends reminders, and uses an AI coach (via OpenRouter) to adaptively manage the training schedule and provide progress feedback.
Built with Node.js/TypeScript, Telegraf, SQLite, and node-cron. Designed so a web dashboard (Approach C) can be added later as a read-only layer on the same SQLite file.
---
## Architecture
A single TypeScript process with four internal components:
```
┌─────────────────────────────────────────┐
│ Telegram Bot (Telegraf) │
│ - command routing │
│ - message handling │
└──────────────┬──────────────────────────┘
┌───────┼───────┐
▼ ▼ ▼
┌──────────┐ ┌──────┐ ┌─────────────┐
│ Session │ │ Cron │ │ AI Coach │
│ Logger │ │ Jobs │ │ (OpenRouter)│
└────┬─────┘ └──┬───┘ └──────┬──────┘
│ │ │
└──────────┴─────────────┘
┌──────▼──────┐
│ SQLite │
└─────────────┘
```
**Components:**
- **Bot handler** — Telegraf v4, routes commands and free-text messages
- **Session logger** — writes training sessions to SQLite, validates input
- **Cron jobs** — `node-cron`, sends reminders at configured times
- **AI coach** — OpenRouter via OpenAI-compatible client; reads history from DB, generates coaching and schedule proposals
- **SQLite** — `better-sqlite3`, single file, easy to back up
- **Config** — `.env` for Telegram token, OpenRouter API key, user chat ID, reminder times, preferred model
---
## Data Model
### `sessions`
| Column | Type | Notes |
|---|---|---|
| `id` | INTEGER PRIMARY KEY | |
| `date` | TEXT | YYYY-MM-DD |
| `duration_min` | INTEGER | |
| `notes` | TEXT | Optional free text |
| `created_at` | TEXT | ISO timestamp |
### `schedule`
| Column | Type | Notes |
|---|---|---|
| `id` | INTEGER PRIMARY KEY | |
| `date` | TEXT | YYYY-MM-DD |
| `planned_min` | INTEGER | |
| `status` | TEXT | `pending` \| `completed` \| `skipped` |
| `skip_reason` | TEXT | Optional |
| `ai_rationale` | TEXT | Why the AI scheduled this |
| `created_at` | TEXT | ISO timestamp |
### `settings`
| Column | Type | Notes |
|---|---|---|
| `key` | TEXT PRIMARY KEY | |
| `value` | TEXT | |
Used for: reminder times, preferred OpenRouter model, training goal, reminder enabled flag.
**Session logging** writes to `sessions` and updates the matching `schedule` row to `completed`. The AI reads both tables to understand history and the current plan.
The existing `.ics` calendar is not imported. The AI generates a fresh adaptive schedule on first run, seeded by onboarding answers.
---
## Commands
### Logging (no AI, always fast)
| Command | Example | Action |
|---|---|---|
| `/log <duration>` | `/log 2h`, `/log 90m` | Record a completed session for today |
| `/skip [reason]` | `/skip work trip` | Mark today as skipped |
| `/snooze` | `/snooze` | Postpone today's reminder by 2 hours |
### Status & history
| Command | Action |
|---|---|
| `/status` | Today's plan, completion state, next reminder time |
| `/history` | Last 7 sessions (date, duration, notes) |
| `/streak` | Current streak and longest streak |
### AI-powered
| Command | Action |
|---|---|
| `/progress` | AI analyzes recent weeks, gives assessment and flags patterns |
| `/next` | AI proposes the next training step (duration or frequency change) |
| `/reschedule [note]` | AI regenerates upcoming schedule; optional context ("busy next week") |
| `/chat <message>` | Free-form coaching conversation |
### Setup
| Command | Action |
|---|---|
| `/setup` | First-run onboarding: current level, typical availability, goal — generates initial schedule |
| `/settings model <id>` | Switch OpenRouter model at runtime without redeploying |
**Fallback:** Unrecognized messages prompt the user to use `/chat` for free-form questions.
**Error handling:** AI commands show a clear error message if OpenRouter is unavailable. Logging commands (`/log`, `/skip`) always work regardless of AI availability.
---
## AI Integration
**Client:** `openai` npm package pointed at `https://openrouter.ai/api/v1`. Model is read from `settings` at runtime.
**Context sent on every AI call:**
- Training goal (from setup)
- Last 30 sessions (date, duration, notes)
- Upcoming schedule with statuses
- Recent skips and their reasons
- Current streak
**Default model:** `google/gemini-2.5-flash-preview`
Chosen for strong reasoning, low cost, fast responses, and large context window. Swappable at runtime via `/settings model <id>`.
**Alternative models (all via OpenRouter):**
- `deepseek/deepseek-chat` — very cheap, good for analytical tasks
- `meta-llama/llama-3.3-70b-instruct` — free on some providers
- `openai/gpt-4o-mini` — reliable, cheap, structured-prompt-friendly
- `qwen/qwen-2.5-72b-instruct` — strong instruction following, low cost
**Prompt strategy:**
- Coaching prompts live in a `prompts/` directory — tweakable without touching logic
- The AI is instructed to be encouraging but honest, explain its reasoning, and flag overtraining or undertraining
- Schedule proposals are requested as JSON for reliable parsing:
```json
[
{ "date": "2026-05-22", "planned_min": 60, "rationale": "Continuing current level" },
{ "date": "2026-05-24", "planned_min": 60, "rationale": "Rest day between" },
{ "date": "2026-05-26", "planned_min": 75, "rationale": "Step up if 22nd went well" }
]
```
---
## Reminder & Scheduling Logic
### Cron jobs
| Job | Default time | Condition |
|---|---|---|
| Morning reminder | **05:30** | Session scheduled today and status is `pending` |
| Evening nudge | **16:30** | Session scheduled today and status still `pending` |
| Snooze | 2 hours after `/snooze` | One-off, clears itself after firing |
No reminders fire on days with no scheduled session, or on days already `completed` or `skipped`.
### Adaptive schedule generation
`/reschedule` sends full history to the AI, which returns a JSON array of upcoming sessions (714 days). The bot parses this into `schedule` rows. Skip reasons are included in the next `/reschedule` call so the AI accounts for gaps.
---
## Project Structure
```
src/
bot/ # Telegraf setup, command handlers
db/ # SQLite schema, queries
scheduler/ # node-cron jobs
ai/ # OpenRouter client, context builder
prompts/ # Prompt templates
config.ts # Env loading and validation
index.ts # Entry point
.env.example
Dockerfile
docker-compose.yml
.dockerignore
package.json
tsconfig.json
```
---
## Deployment
### Local development
Run directly with `npm run dev` (ts-node or tsx). Uses `.env` file for config.
### Production — Docker
The app runs as a Docker container on the home server. SQLite data is persisted via a named volume mounted at `/app/data/`.
**Dockerfile** — multi-stage build:
1. `builder` stage: installs all dependencies, compiles TypeScript to `dist/`
2. `runner` stage: copies only `dist/` and production `node_modules`, runs as a non-root user
**docker-compose.yml** — single service definition:
- Mounts a named volume for the SQLite file (`./data:/app/data`)
- Loads env vars from `.env` (not baked into the image)
- `restart: unless-stopped` so it survives reboots
**SQLite file location:** `/app/data/assistant.db` inside the container, mapped to `./data/assistant.db` on the host — easy to back up by copying that file.
**`.dockerignore`** excludes: `node_modules/`, `src/`, `.env`, `data/`, `*.md`.
**Timezone:** Container timezone set via `TZ` env var (e.g. `TZ=Europe/Amsterdam`) so cron jobs fire at the correct local times.
**Updating:** `docker compose pull && docker compose up -d --build` rebuilds from source and restarts with zero downtime for a single-container setup.
---
## Future Extension Points
- **Web dashboard (Approach C):** Read-only layer on the same SQLite file — no schema changes needed
- **Multi-user:** `settings` and `schedule` tables can gain a `user_id` column
- **Export:** Sessions table trivially exportable to CSV or JSON