Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
33 changes: 9 additions & 24 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
# Required for local plan generation with the default GPT/OpenAI model configuration.
OPENAI_API_KEY=

# Optional alternative: set AI_MODE=anthropic and provide this instead of OPENAI_API_KEY.
ANTHROPIC_API_KEY=

# Optional observability. If set, traces can include prompt/response content.
LANGSMITH_API_KEY=
LANGSMITH_PROJECT=paced_coach_local

# Model routing. Options: cost_effective, standard, development, pro, anthropic.
# OpenAI model routing. Options: cost_effective, standard, development, pro.
AI_MODE=cost_effective

# Local service wiring.
Expand All @@ -27,6 +25,12 @@ DATABASE_NAME=paced_coach
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/paced_coach
REDIS_URL=redis://localhost:6379/0

# Durable Head Coach execution state. Checkpoints use the same local Postgres database.
HEAD_COACH_CHECKPOINT_RETENTION_DAYS=7
HEAD_COACH_CHECKPOINT_POOL_MIN_SIZE=1
HEAD_COACH_CHECKPOINT_POOL_MAX_SIZE=4
HEAD_COACH_CHECKPOINT_POOL_TIMEOUT_SECONDS=10

# Existing local DB preservation. Leave empty for fresh installs.
LOCAL_OWNER_USER_ID=
LOCAL_OWNER_KEY=local-owner
Expand All @@ -40,29 +44,10 @@ LOCAL_USAGE_SAFETY_BYPASS=false
LOCAL_USAGE_DEV_BYPASS=false

# Worker task execution limits in seconds.
ANALYSIS_TASK_TIME_LIMIT_SECONDS=1200
ANALYSIS_TASK_SOFT_TIME_LIMIT_SECONDS=1170
ANALYSIS_TASK_TIME_LIMIT_SECONDS=1800
ANALYSIS_TASK_SOFT_TIME_LIMIT_SECONDS=1770
ANALYSIS_TASK_STALE_GRACE_SECONDS=60

# Provider credential encryption. Required only when saving Strava/WHOOP tokens.
# Generate with:
# pixi run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
FERNET_KEY=

# Optional Strava OAuth.
STRAVA_OAUTH_ENABLED=false
STRAVA_OAUTH_CLIENT_ID=
STRAVA_OAUTH_CLIENT_SECRET=
STRAVA_OAUTH_REDIRECT_URI=http://localhost:3000/app/api/oauth/strava/callback
NEXT_PUBLIC_STRAVA_OAUTH_ENABLED=false

# Optional WHOOP OAuth.
WHOOP_OAUTH_ENABLED=false
WHOOP_OAUTH_CLIENT_ID=
WHOOP_OAUTH_CLIENT_SECRET=
WHOOP_OAUTH_REDIRECT_URI=http://localhost:3000/app/api/oauth/whoop/callback
NEXT_PUBLIC_WHOOP_OAUTH_ENABLED=false

# Coach chat and local API behavior.
COACH_THREAD_ITERATION_LIMIT=15
API_FETCH_TIMEOUT_MS=30000
26 changes: 24 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,31 @@ jobs:
!contains(github.event.head_commit.message, '[ci skip]')
)
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: paced_test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d paced_test"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
NEXT_TELEMETRY_DISABLED: "1"
API_BASE_URL: "http://127.0.0.1:8000"
OPENAI_API_KEY: "sk-TEST"
AI_MODE: "cost_effective"
APP_ENV: "test"
AUTH_MODE: "local"
REDIS_URL: "redis://127.0.0.1:6379/0"
DATABASE_URL: "postgresql+asyncpg://postgres:postgres@127.0.0.1:5432/paced_test"
HEAD_COACH_TEST_DATABASE_URL: "postgresql://postgres:postgres@127.0.0.1:5432/paced_test"
HEAD_COACH_MIGRATION_TEST_ADMIN_URL: "postgresql://postgres:postgres@127.0.0.1:5432/postgres"
steps:
- uses: actions/checkout@v6
with:
Expand Down Expand Up @@ -81,7 +99,11 @@ jobs:
- name: Type Check (mypy)
run: pixi run type-check

- name: Apply test database migrations
run: pixi run alembic -c alembic.ini upgrade head

- name: Head Coach PostgreSQL durability and migration tests
run: pixi run pytest -q tests/test_head_coach_postgres_integration.py tests/test_release_migration_contract.py

- name: Run tests
env:
ANTHROPIC_API_KEY: "sk-ant-TEST"
run: pixi run test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Thumbs.db
.coverage
coverage.xml
htmlcov/
# Redacted scanner reports and isolated release-audit exports
.tmp/

# Logs and runtime artifacts
Expand Down
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Mission
You are assisting with an AI service that:
1. **Reads training context and activity history** from users and connected sources.
1. **Reads athlete-declared context and locally owned plans, calendar state, competitions, and coaching history.** The v2.2 runtime has no external training-data connectors.
2. **Runs an agentic workflow** (LangGraph) on that context.
3. **Returns coaching outputs** (season roadmap, 28-day execution block, insights, and adaptations).
**Architecture**: Python Core (FastAPI/Celery/LangGraph) + Next.js frontend, running local-first by default.
Expand Down Expand Up @@ -47,6 +47,8 @@ Before starting a complex task:

This repo uses a lightweight planning system under `agents_docs/roadmap/`.

Verified engineering learnings live in `docs/solutions/`, organized by category with searchable YAML frontmatter (`module`, `tags`, `problem_type`). They are relevant when implementing or debugging in documented areas.

- **Near-term execution:** `agents_docs/roadmap/now.md` (next 7–14 days)
- **Longer-term direction:** `agents_docs/roadmap/roadmap.md` (3–6 months)
- **Key decisions:** `agents_docs/roadmap/decision_log.md`
Expand Down
17 changes: 12 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
# Changelog

## Unreleased
## 2.2.0 - Unreleased

- Reset the project to a local-first open-source app baseline.
- Removed hosted auth, payment, deployment, internal video, social-story, and generated personal artifact surfaces from the public tree.
- Replaced historical database migrations with a single local-first baseline migration for fresh installs.
- Kept Strava and WHOOP as optional connected-data providers; manual profile and competition setup remains the default first useful path.
- Turned paced.coach into a complete local-first endurance coaching app: describe your training context, generate a season roadmap and 28-day execution block, then keep working with the coach in chat.
- Made the useful first run wearable- and training-data-provider-free. An OpenAI API key and athlete-declared profile, goals, availability, and constraints are enough; no wearable is required.
- Replaced the provider-shaped multi-expert planning graph with one durable Head Coach runtime for initial plans, plan refreshes, coach chat, recap, daily adaptation, and memory extraction.
- Added schema-v3 Season Strategy and 28-day Execution artifacts with rich semantic React components, bounded model self-repair, durable clarification/resume, and version-safe proposal previews.
- Added PostgreSQL LangGraph checkpoints with owner-scoped execution IDs, restart-safe resume, commit-once publication, and bounded terminal retention.
- Made the compact 28-day calendar the primary plan surface while keeping coach rationale and rich semantic guidance available through progressive disclosure.
- Shipped v2.2.0 provider-free: external training-data OAuth, automated source sync, and import surfaces are not part of the public runtime.
- Reset the public repository to an open-source baseline without hosted auth, payment, deployment, private athlete data, or generated personal artifacts.
- Replaced historical database migrations with a local-first baseline and an additive checkpoint-table upgrade for fresh installs.
- Removed the hand-written tool loop, dedicated deep-reasoning formatter agents, unsafe plotting tools, and the `legacy_v1` generation fallback.
- Updated DOMPurify and js-yaml and retained the full frontend, backend, build, and version-governance CI gates.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ git diff --check

## Product Boundaries

- Manual Mode must work without Strava/WHOOP.
- Provider-free planning and coach chat must work from athlete-declared context alone.
- Connected Mode is optional.
- No-provider outputs must not invent activity, load, sleep, HRV, recovery, or readiness claims.
- The public setup path must not require hosted auth, hosted payments, vendor deployment accounts, or production infrastructure accounts.
Expand Down
8 changes: 6 additions & 2 deletions LEGAL_TODO.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# LEGAL TODO

Status date: 2026-06-13
Status date: 2026-07-13
Scope: `web/app` public legal pages (`/impressum`, `/privacy`, `/terms`, `/support`, `/delete`)

## Local-First OSS Review
Expand All @@ -18,7 +18,9 @@ Scope: `web/app` public legal pages (`/impressum`, `/privacy`, `/terms`, `/suppo

## Privacy Hardening

- [x] Document the local-first processor posture: local infrastructure, AI APIs, Strava, WHOOP, no hidden telemetry, and optional LangSmith.
- [x] Document the local-first processor posture: local infrastructure, configured AI APIs, no hidden telemetry, and optional LangSmith.
- [x] Review the current Strava and WHOOP API terms and remove both connectors from the v2.2.0 public runtime and launch claims.
- [ ] Reassess a future connector only after written provider permission or a clearly compatible API contract is documented.
- [x] Add cookie/tracking wording that reflects the current essential-technology posture.
- [ ] Verify exact hosting and storage regions before publishing any region-specific privacy claim.
- [ ] Verify backup retention wording against the current local setup.
Expand All @@ -33,4 +35,6 @@ Scope: `web/app` public legal pages (`/impressum`, `/privacy`, `/terms`, `/suppo
## External Review

- [ ] Run one legal review by a Germany-based lawyer before broad public distribution.
- [ ] Review the `v2.2.0` release candidate's `/impressum`, `/privacy`, `/terms`, `/support`, and `/delete` pages against the local-first distribution model.
- [ ] Apply counsel-required corrections, then rerun the public release audit and exact-commit CI before publication.
- [ ] Recheck all legal pages after each major product change, especially new data sources or managed hosting.
68 changes: 19 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
# paced.coach

Local-first AI endurance coaching app for self-coached athletes.
Your season roadmap, next 28 days, and an AI coach that stays with the plan.

paced.coach gives you a full web app, FastAPI backend, Celery worker, local Postgres/Redis stack, and LangGraph-based coaching workflows. It runs on your machine by default. There is no hosted auth, no hosted payments, no production deploy requirement, and no required training-provider connection.
Describe your goals, training history, availability, and constraints. paced.coach turns that athlete-declared context into a personal season strategy and a day-by-day execution block, then carries the same context into coach chat.

You bring one LLM key. Strava and WHOOP OAuth are optional when you want connected daily sync and weekly recaps.
**No wearable required.** You bring an OpenAI API key and the context only you know. Version 2.2.0 is deliberately provider-free: no activity-platform or recovery-device account is connected to the app.

Not affiliated with Strava or WHOOP. Not medical advice.
The complete app runs on your machine by default: Next.js frontend, FastAPI backend, Celery/LangGraph coaching workflows, and local Postgres/Redis. There is no hosted auth, hosted payment, or production deployment requirement.

Not medical advice.

> **Pre-candidate asset notice:** The README images were generated from sanitized fixture data and contain no local account data, but they show the July pre-Head-Coach renderer. They must be recaptured from the sanitized schema-v3 demo before the v2.2.0 candidate is certified.

![paced.coach local-first AI endurance coach](docs/assets/readme/paced-coach-hero.png)

## Preview

The screenshots below are generated from the public `/demo` route using sanitized fixture data. They do not read a local database or real athlete account.
The screenshots below were generated from the public `/demo` route using sanitized fixture data. They do not read a local database or real athlete account.

<p>
<img src="docs/assets/readme/paced-coach-dashboard.png" alt="paced.coach dashboard with daily focus, recovery gates, weekly recap, daily sync, and season progress" width="49%" />
<img src="docs/assets/readme/paced-coach-plan.png" alt="paced.coach generated training plan with season roadmap and 28-day calendar" width="49%" />
<img src="docs/assets/readme/paced-coach-plan.png" alt="paced.coach generated training plan with season roadmap and 28-day calendar" width="100%" />
</p>

![paced.coach coach workspace preview](docs/assets/readme/paced-coach-coach.png)
Expand All @@ -25,19 +28,19 @@ Open the same preview locally at `http://localhost:3000/demo` after `make start`

## What You Get

- A local web app for profile, race calendar, plan generation, active plan review, and coach conversations.
- AI-generated season roadmap plus a 28-day execution block.
- One continuous coaching flow: athlete profile and goals, season roadmap, 28-day execution block, then coach conversations against the actual plan.
- A calendar-first view of every generated session, with the longer season strategy always in reach.
- Versioned plan renderers for coach report, season strategy, and calendar-style weekly plan views.
- Optional Strava and WHOOP OAuth for connected daily sync and weekly recap.
- Explicit confidence boundaries when only declared profile/goals are available.
- A provider-free coaching model that reasons from what the athlete explicitly declares and what the app has generated.
- Explicit confidence boundaries: missing activity, load, sleep, HRV, recovery, and readiness evidence is never invented.
- Local-first data posture: your app database is your local Postgres volume.

## Requirements

- Docker with Docker Compose v2
- Pixi
- Node.js 24 and npm
- One LLM key: `OPENAI_API_KEY` for the default mode, or `ANTHROPIC_API_KEY` with `AI_MODE=anthropic`
- One OpenAI API key: `OPENAI_API_KEY`

## Quick Start

Expand All @@ -49,8 +52,6 @@ cp .env.example .env
cp web/app/.env.example web/app/.env.local

# Edit .env and set OPENAI_API_KEY.
# Alternative: set AI_MODE=anthropic and ANTHROPIC_API_KEY instead.
# Optional for Strava/WHOOP later: generate FERNET_KEY and provider OAuth values.

make setup
make start
Expand All @@ -74,7 +75,7 @@ Open:
5. Read the active plan at `/app/plan`.
6. Ask questions in `/app/coach`.

Strava and WHOOP are not required for this path. Without connected data, the coach must not claim recent load, compliance, HRV, sleep, recovery, or readiness trends.
No wearable is required for this path. Your OpenAI API key plus your declared profile, goals, availability, constraints, and race calendar form the coaching baseline. The coach must not claim recent load, compliance, HRV, sleep, recovery, or readiness trends unless you explicitly provide that information.

## Environment

Expand All @@ -92,38 +93,7 @@ DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/paced_coach
REDIS_URL=redis://localhost:6379/0
```

Anthropic alternative:

```bash
ANTHROPIC_API_KEY=...
AI_MODE=anthropic
```

Optional connected-mode values:

```bash
FERNET_KEY=...
STRAVA_OAUTH_ENABLED=true
STRAVA_OAUTH_CLIENT_ID=...
STRAVA_OAUTH_CLIENT_SECRET=...
STRAVA_OAUTH_REDIRECT_URI=http://localhost:3000/app/api/oauth/strava/callback

WHOOP_OAUTH_ENABLED=true
WHOOP_OAUTH_CLIENT_ID=...
WHOOP_OAUTH_CLIENT_SECRET=...
WHOOP_OAUTH_REDIRECT_URI=http://localhost:3000/app/api/oauth/whoop/callback
```

Generate `FERNET_KEY` with:

```bash
pixi run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
```

Detailed connector setup:

- [Strava local OAuth](docs/local-first/connect-strava.md)
- [WHOOP local OAuth](docs/local-first/connect-whoop.md)
External training-data connectors are intentionally not shipped in v2.2.0. The full plan and coach flow works from declared context alone.

## Local Data

Expand All @@ -143,7 +113,7 @@ pixi run python scripts/local_owner_report.py

Fresh installs use `LOCAL_OWNER_KEY=local-owner` as the stable owner key. Existing databases should prefer `LOCAL_OWNER_USER_ID` when there is already training data.

Fresh public installs use one baseline database migration: `001_initial_local_first`. If you already ran a pre-public branch with older migration revisions, back up your database and follow [docs/local-first/data-preservation.md](docs/local-first/data-preservation.md) before running the app.
Fresh public installs apply the local-first baseline `001_initial_local_first` and then the additive Head Coach checkpoint upgrade `002_head_coach_checkpoints`. If you already ran a pre-public branch with older migration revisions, back up your database and follow [docs/local-first/data-preservation.md](docs/local-first/data-preservation.md) before running the app.

More detail:

Expand Down Expand Up @@ -186,7 +156,7 @@ pixi run worker-beat
```text
api/ FastAPI routes, models, migrations, local owner auth
worker/ Celery app and background plan generation tasks
services/ai/ LangGraph workflows, prompts, schemas, coach agents
services/ai/ Shared Head Coach runtime, semantic profiles, artifacts, and evals
web/app/ Next.js app
docs/local-first/ Local setup, privacy, and data docs
agents_docs/ Internal planning and architecture notes
Expand Down
14 changes: 11 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ Include:
Never commit real credentials. This includes:

- LLM API keys
- Strava/WHOOP OAuth secrets
- provider access or refresh tokens
- database URLs with nonlocal credentials
- Fernet keys
Expand All @@ -41,14 +40,21 @@ Local Postgres data can contain sensitive training and coaching history. Back up
The local app can send data to:

- the configured LLM provider during generation/coaching
- Strava/WHOOP if OAuth is configured
- LangSmith if `LANGSMITH_API_KEY` is set

Leave optional integrations unset if you do not want those network paths.

## Public Release Gate

Before publishing a public release, run a release audit covering:
Before publishing a public release, run the non-destructive audit from a clean release-candidate commit:

```bash
bash scripts/release_audit.sh
```

The audit uses pinned Gitleaks `v8.30.1` scans against isolated copies of tracked files and publishable Git history. It reports ignored local secret/data paths by name only and never scans their contents. Redacted reports and temporary scan repositories stay under ignored `.tmp/release-audit/`.

The gate covers:

- working tree cleanliness
- ignored local secret files
Expand All @@ -57,3 +63,5 @@ Before publishing a public release, run a release audit covering:
- workflow secret references
- screenshots and fixtures
- hosted ops leftovers

If the audit reports a possible secret, rotate the credential first. Do not rewrite history, delete local artifacts, or remove Docker volumes without explicit maintainer approval and a backup/data-preservation check.
Loading