From 21d96794fadddb802417c466fb59852da03d746e Mon Sep 17 00:00:00 2001 From: iliassjabali Date: Sun, 12 Apr 2026 01:43:42 +0100 Subject: [PATCH] docs(examples): make gymcoach example runnable end-to-end Closes #23. The gymcoach example shipped a 280-line agent.yaml with no README, no env-var reference, no services to back its memory/health declarations, and two dangling subagent refs (./agents/observer.yaml and ./agents/reflector.yaml) that did not exist. GitHub browsers clicking examples/ first had no way to know what this was or how to run it. This PR adds: - README.md walking through prerequisites, a one-shell-session quick start (cp .env.example, source env, docker compose up, validate, health, generate), the full file layout, required vs. optional env vars, and links to the migration guide / concept docs. - .env.example covering every $env: and $secret: reference in the manifest with comments on their own lines (inline comments break docker compose's env-file parser). - docker-compose.yml starting Postgres 16 + Redis 7 with healthchecks, matching the existing style of packages/control-plane/docker-compose.yml. - agents/observer.yaml and agents/reflector.yaml as minimal but fully schema-valid sub-agent manifests, so `agentspec health` no longer reports two missing-file failures out of the box. Verified locally: from a clean clone, `cp .env.example .env && docker compose up -d && agentspec validate && agentspec health` completes in 6 seconds and reports the Groq endpoint reachable and both sub-agents resolved. `agentspec audit` still runs and returns the same score. --- examples/gymcoach/.env.example | 42 ++++++++ examples/gymcoach/README.md | 130 ++++++++++++++++++++++++ examples/gymcoach/agents/observer.yaml | 31 ++++++ examples/gymcoach/agents/reflector.yaml | 31 ++++++ examples/gymcoach/docker-compose.yml | 47 +++++++++ 5 files changed, 281 insertions(+) create mode 100644 examples/gymcoach/.env.example create mode 100644 examples/gymcoach/README.md create mode 100644 examples/gymcoach/agents/observer.yaml create mode 100644 examples/gymcoach/agents/reflector.yaml create mode 100644 examples/gymcoach/docker-compose.yml diff --git a/examples/gymcoach/.env.example b/examples/gymcoach/.env.example new file mode 100644 index 0000000..b3e0275 --- /dev/null +++ b/examples/gymcoach/.env.example @@ -0,0 +1,42 @@ +# GymCoach example environment +# +# Copy to .env and fill in the values. Only GROQ_API_KEY is strictly required +# for `agentspec validate`, `health`, and `generate` to succeed in local dev. +# Everything else is consumed by the generated runtime (LangGraph, memory, +# observability) once you run the agent. +# +# Note on format: docker compose reads this file and does NOT support inline +# comments after a value, so all comments here live on their own line. + +# ── Model ───────────────────────────────────────────────────────────────────── + +# Required. Free tier available at https://console.groq.com +GROQ_API_KEY= + +# Optional. Fallback model on Groq rate-limit, timeout, or 5xx. +AZURE_OPENAI_API_KEY= + +# ── Memory (started by docker-compose.yml in this directory) ────────────────── + +DATABASE_URL=postgresql://gymcoach:gymcoach@localhost:5432/gymcoach +REDIS_URL=redis://localhost:6379/0 + +# ── Domain config ───────────────────────────────────────────────────────────── + +# metric or imperial +UNIT_SYSTEM=metric + +# ── Auth (optional for local dev) ───────────────────────────────────────────── + +# JWT verification endpoint for the generated REST API. +JWKS_URI= + +# ── Observability (optional) ────────────────────────────────────────────────── + +LANGFUSE_HOST=https://cloud.langfuse.com +LANGFUSE_PUBLIC_KEY= + +# Resolves the manifest reference "secret:langfuse-secret-key". +LANGFUSE_SECRET_KEY= + +OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 diff --git a/examples/gymcoach/README.md b/examples/gymcoach/README.md new file mode 100644 index 0000000..988c40c --- /dev/null +++ b/examples/gymcoach/README.md @@ -0,0 +1,130 @@ +# GymCoach — a production-grade AgentSpec example + +A fitness-coaching agent that exercises the full AgentSpec feature surface in one manifest: fallback model, dual-tier memory, sub-agents, input/output guardrails, evaluation datasets with a CI gate, and Langfuse tracing. Use it as a reference when you are migrating a real agent to `agent.yaml`, or as a starting point to copy and prune. + +## Architecture at a glance + +| Concern | Configured as | +|----------------|-------------------------------------------------------------------------------| +| Model | Groq `llama-3.3-70b-versatile`, with Azure `gpt-4` fallback on rate-limit/5xx | +| Prompts | System prompt from `prompts/system_prompt.txt`, hot-reloadable | +| Tools | 10 function tools (log workout, plan, progress, nutrition, search, ...) | +| MCP | `postgres-db` stdio server | +| Memory | Redis short-term (3600s TTL, 20 turns) + Postgres long-term (90-day TTL) | +| Sub-agents | `observer` (parallel) + `reflector` (sequential), manifests under `agents/` | +| Guardrails | topic-filter, PII detector, prompt-injection in; hallucination/toxicity out | +| Evaluation | deepeval + 2 JSONL datasets (`eval/*.jsonl`), CI gate enabled | +| Observability | Langfuse tracing + OpenTelemetry metrics | +| Compliance | OWASP LLM Top 10 + memory hygiene packs | +| API | REST on port 8000, JWT auth, 60 req/min, streaming enabled | + +Full definition: [`agent.yaml`](./agent.yaml) (280 lines). + +## Prerequisites + +- [`pnpm`](https://pnpm.io) 9+ and Node 20+ (to run the `agentspec` CLI from this monorepo) +- [Docker](https://docs.docker.com/get-docker/) (for the Postgres + Redis services used by memory health checks) +- A free Groq API key from (the only env var the example strictly requires) + +Optional: +- Azure OpenAI key for fallback (`AZURE_OPENAI_API_KEY`) +- Langfuse account for tracing (`LANGFUSE_*`) + +## Quick start + +From the repo root, after `pnpm install && pnpm -r build`: + +```bash +cd examples/gymcoach +cp .env.example .env # fill in GROQ_API_KEY at minimum + +# Export every line of .env into the current shell. The agentspec CLI reads +# variables from the process environment, not the .env file itself. +set -a; . ./.env; set +a + +# 1. Start the declared services (Postgres + Redis) in the background +docker compose up -d +docker compose ps # wait until both services report "healthy" + +# 2. Schema-check the manifest (no I/O) +agentspec validate agent.yaml + +# 3. Runtime health — probes the live Groq endpoint + sub-agent manifests +agentspec health agent.yaml + +# 4. Generate runnable LangGraph code from the manifest +agentspec generate agent.yaml --framework langgraph --output ./agent +``` + +`agentspec generate` writes `agent.py`, `requirements.txt`, `.env.example`, and a `guardrails.py` module into `./agent/`. Install the Python deps and run it per the generated README: + +```bash +cd agent +python -m venv .venv && source .venv/bin/activate +pip install -r requirements.txt +python agent.py +``` + +When you are done, tear the services down: + +```bash +docker compose down -v +``` + +## What the manifest references + +### Required env vars + +| Variable | Used for | Required? | +|-------------------|--------------------------------------------|----------------------------------| +| `GROQ_API_KEY` | Primary model | yes | +| `DATABASE_URL` | Long-term memory (Postgres) | yes (docker-compose default set) | +| `REDIS_URL` | Short-term memory | yes (docker-compose default set) | +| `UNIT_SYSTEM` | Injected into the system prompt template | yes (`metric` or `imperial`) | + +### Optional env vars + +| Variable | Used for | +|--------------------------------|--------------------------------------------------| +| `AZURE_OPENAI_API_KEY` | Fallback model on Groq rate-limit / 5xx | +| `JWKS_URI` | JWT verification on the generated REST API | +| `LANGFUSE_HOST` | Langfuse tracing endpoint | +| `LANGFUSE_PUBLIC_KEY` | Langfuse public key | +| `LANGFUSE_SECRET_KEY` | Resolves `$secret:langfuse-secret-key` | +| `OTEL_EXPORTER_OTLP_ENDPOINT` | OpenTelemetry metrics exporter | + +See [`.env.example`](./.env.example) for the full list with local-dev defaults. + +### Required services + +`docker compose up -d` in this directory starts: + +- **Postgres 16** on `127.0.0.1:5432` (user/password/db all `gymcoach`) for long-term memory +- **Redis 7** on `127.0.0.1:6379` for short-term memory + +Both have healthchecks, so `docker compose ps` will report `healthy` once they are ready to serve. + +## What is in this directory + +| Path | Purpose | +|-----------------------------------|----------------------------------------------------------------------| +| [`agent.yaml`](./agent.yaml) | Top-level manifest consumed by the CLI, SDK, and operator | +| [`prompts/system_prompt.txt`](./prompts/system_prompt.txt) | System prompt with `{{ unit_system }}` + `{{ current_date }}` placeholders | +| [`tools/tool_implementations.py`](./tools/tool_implementations.py) | 10 Python tool stubs referenced by `spec.tools[].module` | +| [`agents/observer.yaml`](./agents/observer.yaml) | Parallel sub-agent that flags injury risk and scope drift | +| [`agents/reflector.yaml`](./agents/reflector.yaml) | Sequential sub-agent that writes a post-session retrospective | +| [`eval/workout-qa.jsonl`](./eval/workout-qa.jsonl) | Eval dataset for general workout Q&A | +| [`eval/exercise-advice.jsonl`](./eval/exercise-advice.jsonl) | Eval dataset for exercise-specific advice | +| [`docker-compose.yml`](./docker-compose.yml) | Local Postgres + Redis for memory and health checks | +| [`.env.example`](./.env.example) | Required and optional env vars | + +## A note on the tool stubs + +[`tools/tool_implementations.py`](./tools/tool_implementations.py) contains **stub functions** that return canned data. They exist so the manifest resolves `$file:tools/tool_implementations.py` to a real symbol and so `agentspec generate` has something to wire up. Replace each function with real database queries, HTTP calls, or domain logic before deploying the generated agent. + +## See also + +- [Migration guide: GymCoach](../../docs/guides/migrate-gymcoach.md) — walks through how this manifest was derived from an existing FastAPI + Groq codebase. +- [Concept: the manifest](../../docs/concepts/manifest.md) — full schema reference. +- [Concept: health checks](../../docs/concepts/health-checks.md) — how `agentspec health` probes each declared dependency. +- [Concept: compliance](../../docs/concepts/compliance.md) — how `agentspec audit` scores this manifest against OWASP LLM Top 10 and memory hygiene rules. diff --git a/examples/gymcoach/agents/observer.yaml b/examples/gymcoach/agents/observer.yaml new file mode 100644 index 0000000..a81d68a --- /dev/null +++ b/examples/gymcoach/agents/observer.yaml @@ -0,0 +1,31 @@ +apiVersion: agentspec.io/v1 +kind: AgentSpec + +metadata: + name: gymcoach-observer + version: 1.0.0 + description: "Sub-agent that monitors GymCoach sessions in parallel and flags anomalies (injury risk, overtraining, bad form)." + tags: [fitness, monitoring, sub-agent] + author: "AgentBoot" + license: MIT + +spec: + model: + provider: groq + id: llama-3.3-70b-versatile + apiKey: $env:GROQ_API_KEY + parameters: + temperature: 0.1 + maxTokens: 200 + + prompts: + system: | + You are the GymCoach observer. You receive each assistant turn in parallel + and must flag any of the following in a single concise line, or return "ok" + when nothing applies: + - injury-risk cues (sharp pain, numbness, joint locking) + - overtraining signs (no rest days, declining performance) + - unsafe form recommendations + - scope drift (topics outside fitness, training, exercise, nutrition) + + tools: [] diff --git a/examples/gymcoach/agents/reflector.yaml b/examples/gymcoach/agents/reflector.yaml new file mode 100644 index 0000000..dc15633 --- /dev/null +++ b/examples/gymcoach/agents/reflector.yaml @@ -0,0 +1,31 @@ +apiVersion: agentspec.io/v1 +kind: AgentSpec + +metadata: + name: gymcoach-reflector + version: 1.0.0 + description: "Sub-agent that runs after a GymCoach session and writes a short retrospective to long-term memory." + tags: [fitness, reflection, sub-agent] + author: "AgentBoot" + license: MIT + +spec: + model: + provider: groq + id: llama-3.3-70b-versatile + apiKey: $env:GROQ_API_KEY + parameters: + temperature: 0.2 + maxTokens: 400 + + prompts: + system: | + You are the GymCoach reflector. You run sequentially after each user + session ends. Read the full conversation and produce a 3-5 bullet + retrospective covering: + - what the user asked for + - what the coach recommended + - anything the coach should remember next time (preferences, constraints, progress) + Keep each bullet under 20 words. Return only the bullets. + + tools: [] diff --git a/examples/gymcoach/docker-compose.yml b/examples/gymcoach/docker-compose.yml new file mode 100644 index 0000000..93f4854 --- /dev/null +++ b/examples/gymcoach/docker-compose.yml @@ -0,0 +1,47 @@ +# Local services for the GymCoach example. +# +# Starts the Postgres + Redis dependencies declared in agent.yaml so you can +# run `agentspec health agent.yaml` and any generated LangGraph code locally +# without setting up databases by hand. +# +# docker compose up -d +# docker compose ps # wait until both services are "healthy" +# docker compose down -v # tear down + remove volumes + +services: + postgres: + image: postgres:16-alpine + environment: + POSTGRES_USER: ${POSTGRES_USER:-gymcoach} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-gymcoach} + POSTGRES_DB: ${POSTGRES_DB:-gymcoach} + ports: + - "127.0.0.1:5432:5432" + volumes: + - gymcoach_postgres:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-gymcoach} -d ${POSTGRES_DB:-gymcoach}"] + interval: 5s + timeout: 3s + retries: 10 + start_period: 5s + restart: unless-stopped + + redis: + image: redis:7-alpine + command: ["redis-server", "--appendonly", "yes"] + ports: + - "127.0.0.1:6379:6379" + volumes: + - gymcoach_redis:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 10 + start_period: 3s + restart: unless-stopped + +volumes: + gymcoach_postgres: + gymcoach_redis: