# AI Fitness Coach — AI Operator Manual

Version: 2026-08-18

Audience: AI agents, operators, and replacement product/engineering staff

Production base URL: `https://ai-fitness-coach-ten-henna.vercel.app`

## 1. Read this first

AI Fitness Coach is a private health ledger and dashboard, primary-owned by RT, with multi-user API token infrastructure live for a small number of provisioned test users. It is not a general chatbot. OpenClaw, Hermes, or another MCP client talks to this application's MCP server. WhatsApp routing is outside this repository.

The MCP server is the primary write interface:

- Transport: Streamable HTTP
- Endpoint: `POST /api/mcp`
- Authentication: `Authorization: Bearer <token>` — either the shared `WRITE_API_BEARER_TOKEN` (resolves to the primary user) or a per-user token issued by `scripts/provision-user.mjs` (resolves to that user's `user_id`). Use whichever token identifies the account you are acting on behalf of.
- Tool discovery: use MCP `tools/list`
- Human-readable server reference: unauthenticated `GET /api/mcp`
- Browser reference: `/reference`
- This manual: `/ai-operator-manual.md`
- Internal architecture narrative (repo-only, not public): `docs/OPERATOR-MANUAL.md`

Every call to `POST /api/mcp` requires a bearer token. Never print the token, put it in a receipt, commit it, paste it into documentation, or expose it in a URL. Supply it only in the `Authorization` request header.

The server tool schema returned by `tools/list` is authoritative. If this manual and a live schema disagree, follow the live schema and report the documentation drift.

## 2. Release-state warning

This manual describes the live production system as of the version date above, not a fixed PR range — the codebase moves fast and a dated PR list here goes stale immediately. For the exact current `main` head SHA and what shipped in the most recent session, read `tasks.md` under `## Last Session`, which is the canonical up-to-date state — this manual is not.

Do not claim a capability is production-verified merely because it is documented or deployed. Check the deployed application or the relevant receipt.

## 3. Operating rules for an AI

1. Send all evidence actually available: description, photos, exact label values, caller estimates, quantities, timestamps, and source.
2. Never invent a photo, measurement, calorie value, protein value, timestamp, or evidence-disclosure flag.
3. Use a stable `idempotency_key` for retries of the same meal. Never reuse that key for a different meal.
4. Preserve the user's stated meal time. For a meal more than about ten minutes in the past, send `confirm_backdate: true`.
5. Prefer immutable correction tools. Do not overwrite or delete nutrition history.
6. Return the server's receipt to the user. Do not replace it with an unsupported success claim.
7. Treat `pending` and `processing` as unfinished, not failed.
8. Treat `failed` as persisted failure requiring diagnosis or retry. Do not silently relabel it as success.
9. Use the receipt's row ID and verify URL for drill-down.
10. Do not ask the product owner to run technical commands that the operator or coding agent can run.

## 4. Connecting to MCP

Use the production base URL plus `/api/mcp` and the configured write token. A generic JSON-RPC tool call looks like this:

```http
POST /api/mcp HTTP/1.1
Host: ai-fitness-coach-ten-henna.vercel.app
Authorization: Bearer <WRITE_API_BEARER_TOKEN>
Accept: application/json, text/event-stream
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": "unique-request-id",
  "method": "tools/call",
  "params": {
    "name": "list_meals",
    "arguments": {
      "start_date": "2026-08-10",
      "end_date": "2026-08-10"
    }
  }
}
```

Responses may be JSON or an MCP event stream. Find the text item in `result.content`; it contains the durable receipt or tool result.

Expected authentication behavior:

- Correct bearer token: request reaches MCP.
- Missing, blank, or incorrect token: HTTP `401 Unauthorized`.
- `GET /api/mcp`: no bearer token required because it is documentation only and cannot mutate data.

## 5. Core meal workflow

### 5.1 Log a meal

Tool: `log_meal`

Minimum valid call:

```json
{
  "description": "two eggs and one slice of toast",
  "idempotency_key": "source-message-or-event-id"
}
```

High-quality call with actual evidence:

```json
{
  "description": "Greek yogurt with blueberries",
  "idempotency_key": "stable-upstream-event-id",
  "images": ["data:image/jpeg;base64,<BASE64_DATA>"],
  "agent_estimated_calories_kcal": 310,
  "agent_estimated_protein_g": 24,
  "value_source": "agent_estimate",
  "evidence_disclosure": {
    "has_photo": true,
    "has_agent_estimate": true,
    "has_exact_values": false
  }
}
```

Use `components` when the foods and values are independently known:

```json
{
  "description": "chicken breast, rice, and broccoli",
  "idempotency_key": "stable-upstream-event-id",
  "components": [
    {
      "description": "chicken breast",
      "quantity": 180,
      "unit": "g",
      "agent_estimated_calories_kcal": 300,
      "agent_estimated_protein_g": 55,
      "value_source": "agent_estimate"
    },
    {
      "description": "cooked rice",
      "quantity": 1,
      "unit": "cup",
      "agent_estimated_calories_kcal": 205,
      "agent_estimated_protein_g": 4,
      "value_source": "agent_estimate"
    },
    {
      "description": "broccoli",
      "quantity": 1,
      "unit": "cup",
      "agent_estimated_calories_kcal": 55,
      "agent_estimated_protein_g": 4,
      "value_source": "agent_estimate"
    }
  ]
}
```

Important fields:

- `description`: required, plain-language description, maximum 1000 characters.
- `idempotency_key`: recommended for every caller that can retry.
- `external_id`: alternative stable caller-owned replay identifier.
- `images`: base64 strings or base64 data URLs. Send every genuinely available photo.
- `exact_calories_kcal`, `exact_protein_g`: use only for labels, menus, receipts, or other genuinely exact sources.
- `agent_estimated_calories_kcal`, `agent_estimated_protein_g`: send the caller's estimate when one was actually produced.
- `value_source`: `label`, `historical`, `agent_estimate`, `nim_estimate`, `manual`, or `unknown`.
- `meal_time`: ISO 8601 with an explicit offset.
- `confirm_backdate`: required when intentionally backdating beyond the allowed tolerance.

Evidence disclosure is optional. A true disclosure must be backed by matching payload data. The server derives receipt quality from what actually arrived, not from unsupported booleans.

### 5.2 Understand the meal receipt

A successful receipt contains:

- terminal state such as `LOGGED`, `REPLAYED`, or an error;
- meal group ID;
- number of component rows;
- data-quality assessment;
- each component row ID;
- canonical calories and protein;
- exact, agent, historical, and backend estimate information;
- winning source;
- estimate status;
- a `/verify/{row-id}` URL;
- running daily totals.

The receipt also includes explicit `Images received`, `Quality tier`, guidance, and `heic_converted` fields.

Quality tiers:

- `high`: photo plus exact values or an agent estimate.
- `medium`: at least one of photo, exact values, or an agent estimate.
- `low`: text only. This is accepted when no other evidence exists.

### 5.3 Estimate state

- `pending`: backend enrichment or comparison is queued.
- `processing`: a worker claimed the row and is currently handling it.
- `succeeded`: backend processing completed, or a trusted historical/calibration path completed without needing NIM.
- `failed`: the failure and retry metadata were persisted.

Winning-source labels:

- `label`: exact/canonical label, menu, receipt, or manual source.
- `agent`: caller-provided estimate won.
- `backend_nim`: NVIDIA backend estimate won.
- `historical`: a prior matching meal or calibration won.
- `legacy`: older stored behavior.
- `none`: no usable estimate won.

An exact or caller-provided canonical value is not overwritten merely because the backend comparison differs. The backend estimate is stored separately for comparison.

### 5.4 Retry pending nutrition

Tool: `process_pending_nutrition`

```json
{
  "limit": 10
}
```

The allowed limit is 1 through 50. The receipt reports checked, processed, failed, and skipped rows. A scheduled retry timestamp does not guarantee an independent cron invocation; pending work can also be processed by this tool, `/api/nutrition/process`, or the dashboard's pseudo-cron behavior.

### 5.5 Estimate an existing meal without duplicating it

Tool: `estimate_meal`

```json
{
  "meal_id": "<nutrition-log-row-uuid>",
  "process_now": true
}
```

Optional `images` may be supplied. If omitted, the tool can reuse stored log images. This tool stores backend comparison data against the existing row; it does not insert a duplicate food row.

## 6. Correct, annotate, void, and find meals

### 6.1 Find meals

Tool: `list_meals`

```json
{
  "start_date": "2026-08-01",
  "end_date": "2026-08-10",
  "keyword": "chicken",
  "limit": 50
}
```

This is read-only. It returns active rows and does not run estimation.

### 6.2 Correct a meal

Tool: `correct_meal`

```json
{
  "original_row_id": "<nutrition-log-row-uuid>",
  "correction_text": "This was grilled salmon, not chicken",
  "images": ["data:image/jpeg;base64,<BASE64_DATA>"]
}
```

Corrections are immutable: the original remains in the ledger and the new row references it. Use the new receipt URL for the active record.

### 6.3 Annotate a meal

Tool: `annotate_meal`

```json
{
  "meal_id": "<nutrition-log-row-uuid>",
  "context": "Ate half before the workout and half afterward"
}
```

Annotation copies calories and protein and does not rerun NIM.

### 6.4 Void a duplicate or invalid meal

Tool: `void_meal`

```json
{
  "meal_id": "<nutrition-log-row-uuid>",
  "reason": "Duplicate log",
  "idempotency_key": "void:<nutrition-log-row-uuid>"
}
```

Voiding inserts an immutable zero-value superseding row. It does not delete history. Active totals must exclude the superseded original and the VOID row.

## 7. Photos and HEIC

For deployed native HEIC support:

- accepted photo inputs include base64 bytes and base64 data URLs;
- maximum 5 images per meal request;
- maximum 3 MiB combined decoded image bytes;
- supported HEIC/HEIF brands include `heic`, `heix`, `hevc`, and `mif1`;
- EXIF is extracted from the original bytes before conversion;
- HEIC is converted server-side to JPEG;
- the converted JPEG is stored for backend vision and receipt display;
- the receipt reports `heic_converted: N`;
- JPEG and PNG inputs pass through unchanged.

Telegram or another transport may strip EXIF before the file reaches this server. `heic_converted: 1` proves conversion, not that timestamp or GPS metadata survived the upstream transport.

## 8. Dashboard and receipt pages

Routes:

- Dashboard: `/dashboard`
- Receipt: `/verify/{row-id}`
- Manual meal entry: `/log-meal`
- System reference: `/reference`

Dashboard meal telemetry provides:

- every active selected-day meal row, with no silent four-row truncation;
- localized timestamp;
- estimate-status badge;
- winning-source label;
- photo count;
- VOID visibility without inclusion in totals;
- a tappable row linking to `/verify/{row-id}`.

Status badge colors:

- red: failed;
- yellow: pending or processing;
- green: succeeded;
- grey: agent-only result with no backend estimate.

The receipt page is the diagnostic surface. It shows sanitized persisted error detail for failed estimates: the model, provider URL, HTTP status, and message when available, but never the raw provider response body. It also shows attempt count, last attempt, next retry, backend model/path, exact estimate, agent estimate, and backend estimate.

Do not infer a failure reason from a dashboard badge alone. Open the receipt.

## 9. Apple Health imports

### 9.1 Browser import

Route: `/import/apple-health`

Operator workflow:

1. Open the route.
2. Paste the configured write token.
3. Select a HealthAutoExport `.json` or `.zip` file.
4. Confirm the selected filename.
5. Select **Upload import**.
6. Read the status/error message.
7. Use **Refresh history** until an asynchronous job reaches a terminal state.

Routing rules:

- JSON at or below 4 MiB: direct import.
- ZIP at any size: Blob upload path because extraction is server-side.
- JSON above 4 MiB: Blob upload path.
- Browser Blob import ceiling: 50 MiB.
- ZIP or large JSON requires `BLOB_READ_WRITE_TOKEN` to be configured on the deployment.
- A failed or timed-out preflight blocks ZIP/large-file upload but does not block a small direct JSON import.

### 9.2 Inline MCP import

Tool: `import_apple_health`

```json
{
  "payload": {
    "data": {
      "metrics": [],
      "workouts": []
    }
  }
}
```

Send the full authentic HealthAutoExport JSON object. The tool returns an immutable import receipt with row counts, date range, metric names, and verify URL.

### 9.3 OpenClaw document handoff

Tool: `import_apple_health_document`

```json
{
  "original_filename": "HealthAutoExport.zip",
  "size_bytes": 12345678,
  "content_type": "application/zip",
  "file_url": "https://<allowed-host>/<document>"
}
```

Provide exactly one of `blob_url` or `file_url`. Declared maximum is 100 MiB for this handoff. The result is asynchronous; retain the returned `job_id` and poll import-job status.

## 10. Body measurements and manual metrics

### 10.1 Body-measurement browser import

Route: `/import/body-measurements`

Paste the write token, choose a scale `.xlsx` workbook, verify the selected filename, choose the source label, and upload. The receipt reports parsed, inserted, updated, and skipped counts.

### 10.2 Body-measurement MCP import

Tool: `import_body_measurements`

```json
{
  "payload": {
    "measurements": [
      {
        "measured_at": "2026-08-10T08:00:00-06:00",
        "weight_kg": 80.2,
        "body_fat_pct": 19.4
      }
    ]
  },
  "source": "body_scale"
}
```

Use the authentic device/export payload shape. Do not manufacture missing measurements.

### 10.3 Track a manual metric

Tool: `track_metric`

```json
{
  "metricType": "water",
  "value": 500,
  "unit": "mL"
}
```

Allowed categories are `weight`, `sleep`, `mood`, `steps`, `water`, and `caffeine`. Weight is stored in body measurements; the other categories are stored in health metrics.

## 11. Read history, guidance, and timezone

### Read user history

Tool: `get_user_history`

```json
{
  "metricType": "all",
  "days": 7
}
```

The lookback range is 1 through 90 days.

### Get nutrition guidance

Tool: `get_nutrition_guidance`

```json
{
  "goal": "weight_loss",
  "dietaryRestrictions": ["vegetarian"]
}
```

Allowed goals: `weight_loss`, `muscle_gain`, `maintenance`, and `general_health`.

### Set calendar timezone

Tool: `set_user_timezone`

```json
{
  "timezone": "America/Edmonton"
}
```

Use a valid IANA timezone. It controls date buckets, dashboard day boundaries, and meal/history windows. Allow up to 60 seconds for per-instance settings caches to refresh.

## 12. Troubleshooting

### `401 Unauthorized`

Cause: missing, blank, or incorrect bearer token.

Action: send the configured token in the `Authorization` header. Never put it in JSON arguments or the URL.

### `413 FUNCTION_PAYLOAD_TOO_LARGE`

Cause: the request envelope exceeded the hosting platform limit.

Action: reduce photo size/count. Use Blob upload for large Apple Health exports.

### Meal is `pending`

Cause: backend enrichment/comparison has not completed.

Action: wait briefly, open the receipt, or invoke `process_pending_nutrition` when appropriate.

### Meal is `processing`

Cause: a worker has claimed it.

Action: do not create a duplicate meal. Recheck the same row.

### Meal is `failed`

Cause: provider, model, payload, decoding, or persistence error stored in `estimate_error`.

Action: open `/verify/{row-id}`. Inspect status, attempt count, provider/model, sanitized error, and retry schedule. Retry the existing row rather than logging a duplicate.

### NVIDIA returns HTTP 410

Cause: selected hosted model reached end of life.

Action: this is a model-configuration failure, not proof that the photo was missing. Use the deployed replacement model and reprocess existing failed rows.

### ZIP upload says Blob is unavailable

Cause: `BLOB_READ_WRITE_TOKEN` is missing or preflight failed.

Action: configure Blob through the authorized deployment workflow, or use a small raw JSON export when possible.

### HEIC converts but EXIF is empty

Cause: metadata was absent or stripped upstream, commonly by messaging transports.

Action: preserve and send the original iPhone file. Do not invent timestamp or GPS values.

### Duplicate meal

Cause: retry was sent without a stable idempotency key.

Action: use `void_meal` for the duplicate and use stable replay keys for future retries.

## 13. Handoff prompt for another AI

Give the AI this manual URL and the MCP endpoint, then use this instruction:

```text
Read the entire AI Fitness Coach operator manual before acting. Use the live MCP tools/list schema as authoritative. Every POST /api/mcp call requires the bearer token in the Authorization header. Never reveal the token. Never invent health evidence or nutrition values. Use idempotency keys for retries, immutable correction/void tools for changes, and return the server receipt and verify URL. Treat pending/processing as unfinished and failed as requiring receipt-level diagnosis.
```

Do not place the real bearer token in that prompt. Configure it through the AI client's secret/credential mechanism.
