A serverless AI fitness coaching assistant delivered via Telegram β built on AWS AgentCore, MiniMax-M2.7, and a fully event-driven Lambda architecture.
FitnessCore lets users track daily nutrition and workouts through natural conversation. The agent understands free-form text, remembers what you told it in previous sessions, chains multiple tool calls to answer complex questions, and sends scheduled morning and evening reminders β all without any persistent servers.
| Feature | How it works |
|---|---|
| Natural language logging | "I just had 200g chicken and did a weightlifting session" β protein and workout logged in one message |
| Daily summary | /today or "how did I do today?" β formatted breakdown of all logged metrics |
| Weekly summary | /week β 7-day totals, averages, workout count, best deficit day |
| Calorie burndown | /remaining β progress bar, % complete, and days-to-goal estimate at current pace |
| Profile management | View and update current weight and target weight at any time |
| Conversational memory | Facts stated in one session ("I'm vegetarian") are recalled in future sessions automatically |
| Complex reasoning | "How close am I to my goal and what should I focus on this week?" β multi-tool chain, personalised answer |
| Scheduled reminders | Morning (8am UTC) motivation and evening (8pm UTC) log-completion check |
| Multi-user | All data scoped by Telegram chat_id; any number of users supported without rearchitecting |
Telegram Users
β
β HTTPS Webhook
βΌ
βββββββββββββββββββββββββββββββββββ
β API Gateway (HTTP API) β
β POST /webhook β
ββββββββββββββββββ¬βββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Lambda: webhook-handler β Validates Telegram secret.
β Python 3.12 Β· timeout 10s β Enqueues message to SQS.
ββββββββββββββββββ¬βββββββββββββββββ Returns 200 in < 3s.
β
βΌ
βββββββββββββββββββββββββββββββββββ
β SQS: agent-queue β Standard queue.
β DLQ after 3 failures β Visibility timeout 300s.
ββββββββββββββββββ¬βββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββ
β Lambda: agent-invoker β SQS consumer.
β Python 3.12 Β· timeout 120s β Runs onboarding wizard for new users.
ββββββββββββββββββ¬βββββββββββββββββ Calls AgentCore for returning users.
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AWS AgentCore Runtime β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Per-session microVM Β· isolated per user_id β β
β β β β
β β 1. Retrieve long-term memories (AgentCore Memory) β β
β β 2. Build system prompt with user context β β
β β 3. Call MiniMax-M2.7 with tool definitions β β
β β 4. AgentCore Gateway executes tool calls server-side β β
β β 5. Feed results back β repeat until end_turn β β
β β 6. Save conversation turn to AgentCore Memory β β
β β 7. Return final text response β β
β βββββββββββββββββββ¬βββββββββββββββββββ¬ββββββββββββββββββββββββ β
β β β β
β ββββββββββββββββββΌββββ ββββββββββββΌββββββββββββββββββββββββββ β
β β AgentCore Memory β β AgentCore Gateway (MCP) β β
β β β β β β
β β Short-term: β β Tools (server-side execution): β β
β β session turns β β β’ log_metric β β
β β β β β’ get_summary β β
β β Long-term: β β β’ get_or_update_profile β β
β β user facts β β β’ get_progress β β
β β preferences β β β’ update_user_memory β β
β β patterns β β β’ onboard_user β β
β ββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β Response text
βΌ
Lambda: agent-invoker β Telegram sendMessage
EventBridge Scheduler
βββ cron(0 8 * * ? *) β Lambda: notification-dispatcher [morning nudge]
βββ cron(0 20 * * ? *) β Lambda: notification-dispatcher [evening check-in]
| Layer | Technology | Notes |
|---|---|---|
| Frontend | Telegram Bot API | Webhook-based; no polling |
| API | AWS API Gateway (HTTP API) | Routes webhook POST to Lambda |
| Compute | AWS Lambda (Python 3.12) | ARM64, on-demand, per-function IAM roles |
| Agent runtime | AWS AgentCore Runtime | Per-session microVM; no persistent compute |
| Agent memory | AWS AgentCore Memory | Short-term (session turns) + long-term (semantic facts) |
| Tool server | AWS AgentCore Gateway (MCP) | Server-side tool execution via MCP protocol |
| LLM | MiniMax-M2.7 | Anthropic-compatible endpoint; no extra translation layer |
| Database | Amazon DynamoDB (on-demand) | Two tables; always scoped by user_id partition key |
| Queue | Amazon SQS | Decouples webhook ack from agent processing |
| Scheduler | Amazon EventBridge Scheduler | Morning and evening cron triggers |
| Secrets | AWS SSM Parameter Store | All API keys stored as SecureString; fetched at runtime |
| IaC | AWS SAM | Single template.yaml defines all infrastructure |
| Observability | CloudWatch Logs + Alarms + X-Ray | Dashboard, 3 alarms, 30-day log retention |
fitnesscore/
βββ template.yaml β AWS SAM β all infrastructure in one file
βββ samconfig.toml.example β deployment config template (copy β samconfig.toml)
βββ pytest.ini
β
βββ src/
β βββ webhook/ β Lambda: Telegram webhook receiver
β β βββ handler.py β validates secret, enqueues to SQS
β β βββ requirements.txt
β βββ agent-invoker/ β Lambda: SQS consumer β AgentCore
β β βββ handler.py β onboarding wizard + AgentCore invocation
β β βββ requirements.txt
β βββ agent/ β AgentCore agent definition
β β βββ agent.py β MiniMax client, memory, tool loop
β β βββ system_prompt.md β agent persona and instructions (single source of truth)
β β βββ Dockerfile β container image for ECR
β β βββ requirements.txt
β βββ tools/ β AgentCore Gateway tool implementations
β β βββ fitness_log.py β log_metric tool
β β βββ fitness_summary.py β get_summary tool
β β βββ fitness_profile.py β get_or_update_profile tool
β β βββ fitness_burndown.py β get_progress tool
β β βββ user_memory.py β update_user_memory tool
β β βββ onboard_user.py β onboard_user tool
β βββ tools-gateway/ β Lambda: AgentCore Gateway dispatcher
β β βββ handler.py
β βββ notifications/ β Lambda: EventBridge-triggered reminders
β βββ dispatcher.py
β
βββ tests/
β βββ conftest.py β shared fixtures (mocked DynamoDB, SSM)
β βββ unit/ β 162 unit tests; moto mocks all AWS calls
β βββ integration/ β end-to-end tests against deployed stack
β βββ integration_config.py.example β copy and fill in your values
β βββ test_e2e.py β webhook β SQS β Lambda β DynamoDB pipeline
β βββ test_agent.py β AgentCore memory and tool-chain tests
β βββ test_reasoning.py β multi-step reasoning scenarios
β
βββ docs/
β βββ ARCHITECTURE.md β detailed architecture and design decisions
β βββ DATA_MODEL.md β DynamoDB schemas, validation rules, examples
β βββ RUNBOOK.md β deploy, rollback, logs, secret rotation, troubleshooting
β
βββ infra/
β βββ iam.yaml β IAM roles reference (least-privilege, per-Lambda)
β βββ dynamodb.yaml β DynamoDB table definitions
β βββ ssm-params.sh β SSM parameter names reference (no values)
β
βββ scripts/
βββ setup-dev.sh β install local dev dependencies
βββ deploy.sh β pytest β sam build β sam deploy pipeline
βββ seed-test-data.sh β seed DynamoDB with realistic test fixtures
- Python 3.12 β
python3 --version - AWS CLI v2 β install guide
- AWS SAM CLI β₯ 1.100 β install guide
- Docker β required for SAM builds with Lambda container images
- An AWS account with permissions to create Lambda, DynamoDB, SQS, API Gateway, IAM, SSM, EventBridge, and AgentCore resources
- A Telegram bot token β create one via @BotFather
- A MiniMax API key β from platform.minimaxi.com
git clone https://github.com/YOUR_USERNAME/fitnesscore.git
cd fitnesscore
bash scripts/setup-dev.sh
source .venv/bin/activateaws configure --profile fitnesscore-dev
# Enter: Access Key ID, Secret Access Key, region (eu-west-1), output (json)# Telegram bot token (from @BotFather)
aws ssm put-parameter \
--name /fitnesscore/telegram/bot_token \
--value "YOUR_BOT_TOKEN" \
--type SecureString \
--profile fitnesscore-dev --region eu-west-1
# Webhook secret (generate a random string)
aws ssm put-parameter \
--name /fitnesscore/telegram/webhook_secret \
--value "$(openssl rand -hex 32)" \
--type SecureString \
--profile fitnesscore-dev --region eu-west-1
# MiniMax API key
aws ssm put-parameter \
--name /fitnesscore/minimax/api_key \
--value "YOUR_MINIMAX_API_KEY" \
--type SecureString \
--profile fitnesscore-dev --region eu-west-1cp samconfig.toml.example samconfig.toml
# Edit samconfig.toml: set your AWS profile nameAgentCore Runtime, Memory, and Gateway must be provisioned before deployment. See docs/ARCHITECTURE.md for the full AgentCore setup steps, including:
- Creating an AgentCore Runtime (container image from ECR)
- Creating AgentCore Memory with a user preferences strategy
- Creating an AgentCore Gateway and registering the 6 fitness tools
- Storing the agent runtime ARN and memory IDs in SSM:
aws ssm put-parameter \
--name /fitnesscore/agentcore/agent_id \
--value "YOUR_AGENT_RUNTIME_ARN" \
--type String \
--profile fitnesscore-dev --region eu-west-1# Runs: pytest tests/unit β sam build β sam deploy
bash scripts/deploy.shOn success, the webhook URL is printed in the CloudFormation outputs. Register it with Telegram:
WEBHOOK_SECRET=$(aws ssm get-parameter \
--name /fitnesscore/telegram/webhook_secret \
--with-decryption --query Parameter.Value --output text \
--profile fitnesscore-dev --region eu-west-1)
BOT_TOKEN=$(aws ssm get-parameter \
--name /fitnesscore/telegram/bot_token \
--with-decryption --query Parameter.Value --output text \
--profile fitnesscore-dev --region eu-west-1)
curl -X POST "https://api.telegram.org/bot${BOT_TOKEN}/setWebhook" \
-H "Content-Type: application/json" \
-d "{
\"url\": \"YOUR_WEBHOOK_URL_FROM_OUTPUTS\",
\"secret_token\": \"${WEBHOOK_SECRET}\",
\"allowed_updates\": [\"message\"]
}"Send /start to your bot in Telegram. You should be greeted by the onboarding wizard within a few seconds.
pytest tests/unit -v
# 162 tests Β· moto mocks all DynamoDB and SSM calls# 1. Seed test data
bash scripts/seed-test-data.sh
# 2. Configure your test values
cp tests/integration/integration_config.py.example tests/integration/integration_config.py
# Edit integration_config.py with your ARNs and test user ID
# 3. Run
AWS_PROFILE=your-profile pytest tests/integration/test_e2e.py tests/integration/test_agent.py -v -sTwo DynamoDB tables, both with on-demand billing and at-rest encryption:
fitnesscore-users β one record per registered user
| Attribute | Type | Description |
|---|---|---|
user_id (PK) |
String | Telegram chat_id |
first_name |
String | Telegram first name |
current_weight |
Number | Current weight in kg |
target_weight |
Number | Goal weight in kg |
voice_enabled |
Boolean | Daily voice note opt-in |
video_enabled |
Boolean | Weekly video opt-in |
active |
Boolean | Include in notification fan-out |
registered_at |
String | ISO8601 UTC timestamp |
fitnesscore-daily-logs β one record per user per calendar day
| Attribute | Type | Description |
|---|---|---|
user_id (PK) |
String | Telegram chat_id |
date (SK) |
String | YYYY-MM-DD |
protein |
Number | Grams of protein (0β500) |
calories |
Number | Kcal intake (0β10,000) |
calories_burned |
Number | Kcal burned through exercise (0β5,000) |
deficit |
Number | Calorie deficit, can be negative (β5,000β5,000) |
weightlifting |
Boolean | Weightlifting session completed |
cycling |
Boolean | Cycling session completed |
Full schemas, access patterns, and validation rules in docs/DATA_MODEL.md.
Six tools registered with AgentCore Gateway. Each is a pure function β one DynamoDB operation or one calculation. The agent never sees user_id in tool schemas; it is always injected server-side.
| Tool | What it does |
|---|---|
log_metric |
Write protein/calories/burn/deficit/workout to fitnesscore-daily-logs |
get_summary |
Read today's or this week's metrics from fitnesscore-daily-logs |
get_or_update_profile |
Read or write user profile in fitnesscore-users |
get_progress |
Calculate calorie burndown progress %, kcal remaining, days to goal |
update_user_memory |
Write a long-term user fact to AgentCore Memory |
onboard_user |
Create a new user record in fitnesscore-users |
- No secrets in code β all API keys and tokens are fetched from SSM Parameter Store at runtime
- Webhook validation β every request validated against
X-Telegram-Bot-Api-Secret-Tokenusing constant-time comparison - User isolation β every DynamoDB query includes
user_idas the partition key; no cross-user data access is possible - AgentCore session isolation β one microVM per user session; memory is scoped per
actor_id - Least-privilege IAM β each Lambda has a dedicated role with only the specific actions it needs on specific resources
- Encryption at rest β DynamoDB and SQS both have server-side encryption enabled
Phase 1 runs comfortably within AWS free tier limits for Lambda and SQS. The main costs are AgentCore and MiniMax.
| Scale | Monthly estimate |
|---|---|
| 1 active user | ~$4β7 |
| 10 active users | ~$29β34 |
| 50 active users | ~$90β110 |
Detailed breakdown in docs/ARCHITECTURE.md.
| Document | Contents |
|---|---|
| docs/ARCHITECTURE.md | Full system design, component responsibilities, memory strategy, IAM design, cost model, key decisions |
| docs/DATA_MODEL.md | DynamoDB schemas, attribute types, validation constraints, access patterns |
| docs/RUNBOOK.md | Deploy, rollback, log tailing, secret rotation, troubleshooting guide, known limitations |
- CloudWatch Dashboard:
FitnessCore-Operationsβ Lambda invocations/errors/duration, SQS depth, DLQ count, DynamoDB capacity - CloudWatch Alarms: DLQ > 0 messages, webhook error rate > 5%, agent-invoker p99 latency > 90s β SNS topic
- X-Ray tracing: Active tracing enabled on all Lambda functions
- Log retention: 30 days on all Lambda log groups