@wattsonclub/wattson-cli 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +40 -2
- package/dist/THIRD-PARTY-NOTICES.txt +23 -0
- package/dist/bin.js +8302 -0
- package/package.json +32 -4
- package/skill/SKILL.md +60 -0
- package/skill/references/commands.md +375 -0
- package/skill/references/workout-building-blocks.md +507 -0
- package/skill/references/workout-text-format.md +460 -0
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wattsonclub/wattson-cli",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"wattson": "./dist/bin.js",
|
|
8
|
+
"trainer-planner": "./dist/bin.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"build": "node build.mjs",
|
|
14
|
+
"prepack": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"description": "Wattson training planner CLI",
|
|
17
|
+
"license": "UNLICENSED",
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"skill",
|
|
24
|
+
"README.md",
|
|
25
|
+
"CHANGELOG.md"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"zod": "^3.25.76",
|
|
32
|
+
"esbuild": "^0.25.12"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wattson
|
|
3
|
+
description: Use the Wattson CLI to authenticate a local or remote agent, inspect athlete profiles and uploaded rides, and draft, validate and save structured cycling workouts. Use for terminal automation with Wattson, not remote control of an offline ride.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Wattson
|
|
7
|
+
|
|
8
|
+
## 1. Install and inspect
|
|
9
|
+
|
|
10
|
+
Requires Node.js 22 or newer. Install `npm install -g @wattsonclub/wattson-cli`, then run `wattson --help`. Use the commands advertised by the installed version; do not invent MCP-equivalent commands.
|
|
11
|
+
|
|
12
|
+
The default API is `https://api.wattson.club`. Override with `--base-url`, `WATTSON_API_BASE_URL` or legacy `PLANNER_API_BASE_URL` only for an explicitly chosen environment. Confirm `wattson health` succeeds before continuing.
|
|
13
|
+
|
|
14
|
+
## 2. Authenticate
|
|
15
|
+
|
|
16
|
+
Run `wattson auth status` (exit 1 means signed out, including with `--json`). If not signed in:
|
|
17
|
+
|
|
18
|
+
- On the user's own computer: `wattson auth login` opens browser OAuth with a localhost callback.
|
|
19
|
+
- On an SSH host, server, container, or agent runtime: `wattson auth login --remote` prints a verification URL and a short-lived user code and waits for approval. The user opens that URL on their own phone/laptop, signs in, checks the code and explicitly approves. No inbound port or SSH tunnel is needed.
|
|
20
|
+
|
|
21
|
+
Deliver login URLs and codes privately to the requesting user, never in a shared group. Never request their password, copy browser cookies, approve on their behalf, or expose access/refresh tokens. A timeout or denial means not authenticated; start a new login only at the user's request. For an unattended agent, complete the initial approval interactively; subsequent token refresh is automatic.
|
|
22
|
+
|
|
23
|
+
Verify `wattson auth status` and `wattson --json athlete profile`. `wattson auth logout` clears local credentials. Do not assume that local logout revokes other sessions. Keep the credential directory persistent and private for containers; do not bake credentials into an image.
|
|
24
|
+
|
|
25
|
+
## 3. Inspect before changing
|
|
26
|
+
|
|
27
|
+
Use `--json` for machine consumption and check exit status. Login instructions are interactive information, not a successful login result.
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
wattson --json athlete profile
|
|
31
|
+
wattson --json athlete readiness latest
|
|
32
|
+
wattson --json athlete sessions --limit 10
|
|
33
|
+
wattson --json workout get WORKOUT_ID --version 1
|
|
34
|
+
wattson --json workout revisions WORKOUT_ID
|
|
35
|
+
wattson --json session summary SESSION_ID
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Omit athlete IDs to use the authenticated athlete. Do not infer permission to access someone else's athlete ID.
|
|
39
|
+
|
|
40
|
+
## 4. Draft, validate, save
|
|
41
|
+
|
|
42
|
+
Translate natural language into structured inputs. Read [workout building blocks](references/workout-building-blocks.md) for payload structure and [workout text format](references/workout-text-format.md) for readable previews.
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
wattson --json workout draft --objective vo2max --duration-min 20 --execution-mode erg --style-hint "3 hard intervals"
|
|
46
|
+
wattson --json workout validate --input-file validate.json
|
|
47
|
+
wattson --json workout save --input-file save.json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Draft output is not a save request: construct the appropriate payload described in [command examples](references/commands.md). A draft alone is not published. Fix validation errors before saving. FTP-relative values are fractions: `0.55` means 55%, not `55`.
|
|
51
|
+
|
|
52
|
+
Only save when the user's task authorizes publishing the workout. After saving, verify the returned workout ID/version with `workout get`. Report “saved to your library, ready for the app to download,” not confirmed device delivery. Revisions require validation and save too.
|
|
53
|
+
|
|
54
|
+
## 5. Respect the offline ride model
|
|
55
|
+
|
|
56
|
+
The rider's app downloads saved workouts and runs the selected ride locally. A suspended/offline app syncs when it returns online. Uploaded rides can then be reviewed with `athlete sessions`, `session summary`, `session telemetry` and `session artifacts`.
|
|
57
|
+
|
|
58
|
+
Legacy CLI `plan schedule`, `session load-workout`, `session adapt` and `session complete` are compatibility APIs, not remote start/control of the current app. Do not synthesize a ride completion. The CLI is not full MCP parity: use MCP for library listing/retirement/calendar scheduling and course discovery/import when those commands are absent from `wattson --help`.
|
|
59
|
+
|
|
60
|
+
Finish by verifying the affected read endpoint and summarizing the result. Distinguish a successful local command from device acknowledgement or Strava delivery.
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
> Command examples inherited from the compatibility CLI. Current offline app behavior is described in ../SKILL.md; legacy scheduling/session calls do not remotely control the app.
|
|
2
|
+
|
|
3
|
+
# Planner CLI examples
|
|
4
|
+
|
|
5
|
+
This file exists to make the intended agent/operator flow concrete.
|
|
6
|
+
|
|
7
|
+
Design rule:
|
|
8
|
+
- natural language belongs in the agent layer
|
|
9
|
+
- structured inputs belong in planner-api / planner-cli
|
|
10
|
+
- prefer deterministic request bodies over special `...from-prompt` endpoints
|
|
11
|
+
|
|
12
|
+
Operator defaults:
|
|
13
|
+
- use `wattson --help` to inspect the live command surface
|
|
14
|
+
- add `--json` when the output is meant for automation or another agent step
|
|
15
|
+
- add `--base-url <url>` when you are pointing at a non-default planner-api instance
|
|
16
|
+
|
|
17
|
+
## 0. Sanity-check the surface
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
wattson health
|
|
21
|
+
wattson --json health
|
|
22
|
+
wattson --base-url http://192.168.1.50:3000 health
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 1. Recommend a workout for today
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
wattson recommend daily athlete-1 \
|
|
29
|
+
--available-time-min 20 \
|
|
30
|
+
--prefer-objective vo2max \
|
|
31
|
+
--hard-constraint "indoor only"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 2. Draft a workout from simple flags
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
wattson workout draft athlete-1 \
|
|
38
|
+
--objective vo2max \
|
|
39
|
+
--duration-min 20 \
|
|
40
|
+
--execution-mode erg \
|
|
41
|
+
--style-hint "3 high intensity intervals"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This is the intended translation pattern for agents:
|
|
45
|
+
|
|
46
|
+
- user says: `Create me a 20 min session with 3 high intensity intervals`
|
|
47
|
+
- agent infers:
|
|
48
|
+
- objective: `vo2max`
|
|
49
|
+
- durationMin: `20`
|
|
50
|
+
- allowedExecutionModes: `["erg"]`
|
|
51
|
+
- styleHints: `["3 high intensity intervals"]`
|
|
52
|
+
- agent calls `workout draft`
|
|
53
|
+
|
|
54
|
+
## 3. Draft a workout from a full JSON body
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
wattson workout draft --input-file /tmp/draft-workout.json
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Example `/tmp/draft-workout.json`:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"athleteId": "athlete-1",
|
|
65
|
+
"objective": "vo2max",
|
|
66
|
+
"durationMin": 20,
|
|
67
|
+
"constraints": {
|
|
68
|
+
"allowedExecutionModes": ["erg"],
|
|
69
|
+
"maxPctFtp": 1.2
|
|
70
|
+
},
|
|
71
|
+
"context": {
|
|
72
|
+
"styleHints": ["3 high intensity intervals"]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Note:
|
|
78
|
+
- FTP-relative values are fractional in structured payloads
|
|
79
|
+
- `maxPctFtp: 1.2` means `120% FTP`
|
|
80
|
+
- `target: { "mode": "pct_ftp", "value": 0.55 }` means `55% FTP`
|
|
81
|
+
- never pass whole percentages like `120` or `55`
|
|
82
|
+
|
|
83
|
+
## 4. Revise a workout from a structured patch
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
wattson workout revise --input-file /tmp/revise-workout.json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Example `/tmp/revise-workout.json`:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"patch": {
|
|
94
|
+
"workoutId": "workout-1",
|
|
95
|
+
"baseVersion": 2,
|
|
96
|
+
"preserve": ["objective", "interval_structure"],
|
|
97
|
+
"changes": {
|
|
98
|
+
"durationDeltaSec": -120,
|
|
99
|
+
"intensityMultiplier": 0.95,
|
|
100
|
+
"addCadenceFocus": false,
|
|
101
|
+
"removeLastInterval": false,
|
|
102
|
+
"switchToRecovery": false
|
|
103
|
+
},
|
|
104
|
+
"naturalLanguage": "Keep the same shape, just make it slightly easier and a bit shorter.",
|
|
105
|
+
"reason": "Rider requested a lighter version."
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This is the intended translation pattern for agents:
|
|
111
|
+
- user says: `make it slightly easier`
|
|
112
|
+
- agent chooses a structured patch like:
|
|
113
|
+
- `intensityMultiplier: 0.95`
|
|
114
|
+
- maybe preserve objective / interval structure
|
|
115
|
+
- include the original natural-language request as trace context when helpful
|
|
116
|
+
|
|
117
|
+
## 5. Validate a workout before saving
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
wattson workout validate --input-file /tmp/validate-workout.json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Example `/tmp/validate-workout.json`:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"workout": {
|
|
128
|
+
"id": "workout-1",
|
|
129
|
+
"version": 2,
|
|
130
|
+
"createdAt": "2026-04-09T00:00:00.000Z",
|
|
131
|
+
"updatedAt": "2026-04-09T00:00:00.000Z",
|
|
132
|
+
"createdBy": { "type": "system", "system": "planner_api" },
|
|
133
|
+
"updatedBy": { "type": "system", "system": "planner_api" },
|
|
134
|
+
"kind": "workout_definition",
|
|
135
|
+
"athleteId": "athlete-1",
|
|
136
|
+
"title": "VO2 20",
|
|
137
|
+
"objective": "vo2max",
|
|
138
|
+
"sport": "cycling_indoor",
|
|
139
|
+
"status": "draft",
|
|
140
|
+
"athleteContext": {
|
|
141
|
+
"ftpWatts": 250,
|
|
142
|
+
"constraints": []
|
|
143
|
+
},
|
|
144
|
+
"blocks": [
|
|
145
|
+
{
|
|
146
|
+
"id": "block-1",
|
|
147
|
+
"type": "steady",
|
|
148
|
+
"durationSec": 300,
|
|
149
|
+
"target": { "mode": "pct_ftp", "value": 0.55 },
|
|
150
|
+
"instructions": ["Warm up"]
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
"constraints": {
|
|
154
|
+
"allowedExecutionModes": ["erg"]
|
|
155
|
+
},
|
|
156
|
+
"rationale": {
|
|
157
|
+
"summary": "Short VO2-focused session",
|
|
158
|
+
"whyToday": ["Fits available time"]
|
|
159
|
+
},
|
|
160
|
+
"tags": ["vo2", "short"]
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## 6. Save a validated workout
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
wattson workout save --input-file /tmp/save-workout.json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Example `/tmp/save-workout.json`:
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"workout": {
|
|
176
|
+
"id": "workout-1",
|
|
177
|
+
"version": 2,
|
|
178
|
+
"createdAt": "2026-04-09T00:00:00.000Z",
|
|
179
|
+
"updatedAt": "2026-04-09T00:00:00.000Z",
|
|
180
|
+
"createdBy": { "type": "system", "system": "planner_api" },
|
|
181
|
+
"updatedBy": { "type": "system", "system": "planner_api" },
|
|
182
|
+
"kind": "workout_definition",
|
|
183
|
+
"athleteId": "athlete-1",
|
|
184
|
+
"title": "VO2 20",
|
|
185
|
+
"objective": "vo2max",
|
|
186
|
+
"sport": "cycling_indoor",
|
|
187
|
+
"status": "validated",
|
|
188
|
+
"athleteContext": {
|
|
189
|
+
"ftpWatts": 250,
|
|
190
|
+
"constraints": []
|
|
191
|
+
},
|
|
192
|
+
"blocks": [
|
|
193
|
+
{
|
|
194
|
+
"id": "block-1",
|
|
195
|
+
"type": "steady",
|
|
196
|
+
"durationSec": 300,
|
|
197
|
+
"target": { "mode": "pct_ftp", "value": 0.55 },
|
|
198
|
+
"instructions": ["Warm up"]
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"constraints": {
|
|
202
|
+
"allowedExecutionModes": ["erg"]
|
|
203
|
+
},
|
|
204
|
+
"rationale": {
|
|
205
|
+
"summary": "Short VO2-focused session",
|
|
206
|
+
"whyToday": ["Fits available time"]
|
|
207
|
+
},
|
|
208
|
+
"tags": ["vo2", "short"]
|
|
209
|
+
},
|
|
210
|
+
"expectedValidationStatus": "pass"
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The same `%FTP` rule applies here:
|
|
215
|
+
- use fractional values like `0.55` and `1.2`
|
|
216
|
+
- do not send whole percentages like `55` or `120`
|
|
217
|
+
|
|
218
|
+
## 7. Schedule a saved workout
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
wattson plan schedule athlete-1 \
|
|
222
|
+
--workout-id workout-1 \
|
|
223
|
+
--version 2 \
|
|
224
|
+
--date 2026-04-10
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Note:
|
|
228
|
+
- the CLI takes `athleteId` here and hides the current HTTP `planId` convention (`<athleteId>:current-plan`)
|
|
229
|
+
|
|
230
|
+
## 8. Inspect the compiled runtime plan
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
wattson workout runtime-plan workout-1 --version 2
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## 9. Queue the saved workout into an active trainer session
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
wattson session load-workout \
|
|
240
|
+
--athlete-id athlete-1 \
|
|
241
|
+
--workout-id workout-1 \
|
|
242
|
+
--version 2
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## 10. Complete a session from simple flags
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
wattson session complete session-123 \
|
|
249
|
+
--status completed \
|
|
250
|
+
--completion-kind completed \
|
|
251
|
+
--elapsed-sec 1200 \
|
|
252
|
+
--completed-duration-sec 1200 \
|
|
253
|
+
--compliance-pct 98 \
|
|
254
|
+
--note "Strong finish"
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## 11. Complete a session from a structured JSON body
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
wattson session complete session-123 --input-file /tmp/complete-session.json
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Example `/tmp/complete-session.json`:
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
{
|
|
267
|
+
"status": "completed",
|
|
268
|
+
"completionKind": "completed",
|
|
269
|
+
"startedAt": "2026-04-09T10:00:00.000Z",
|
|
270
|
+
"endedAt": "2026-04-09T10:20:00.000Z",
|
|
271
|
+
"executionMode": "erg",
|
|
272
|
+
"liveSummary": {
|
|
273
|
+
"elapsedSec": 1200
|
|
274
|
+
},
|
|
275
|
+
"actualMetrics": {
|
|
276
|
+
"avgPower": 245,
|
|
277
|
+
"avgHrBpm": 158,
|
|
278
|
+
"maxHrBpm": 173,
|
|
279
|
+
"compliancePct": 98,
|
|
280
|
+
"completedDurationSec": 1200,
|
|
281
|
+
"completedTss": 42
|
|
282
|
+
},
|
|
283
|
+
"notes": ["Strong finish"]
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## 12. Apply a bounded live adaptation
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
wattson session adapt session-123 \
|
|
291
|
+
--op skip_interval \
|
|
292
|
+
--reason "Rider requested it" \
|
|
293
|
+
--payload-json '{"stepIndex":2}'
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Allowed live ops today:
|
|
297
|
+
- `reduce_intensity`
|
|
298
|
+
- `increase_intensity`
|
|
299
|
+
- `extend_recovery`
|
|
300
|
+
- `skip_interval`
|
|
301
|
+
- `shorten_workout`
|
|
302
|
+
- `switch_execution_mode`
|
|
303
|
+
|
|
304
|
+
That means:
|
|
305
|
+
- `skip the next interval` -> yes, good fit
|
|
306
|
+
- `make it a bit easier` -> yes, good fit
|
|
307
|
+
- `add another interval` -> not yet a first-class live op; that would need a richer structural adaptation model later
|
|
308
|
+
|
|
309
|
+
## 13. Inspect the summary after completion
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
wattson session summary session-123
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## 14. Inspect canonical telemetry after completion
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
wattson session telemetry session-123
|
|
319
|
+
wattson --json session telemetry session-123
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
If no telemetry has been persisted yet, JSON mode returns:
|
|
323
|
+
|
|
324
|
+
```json
|
|
325
|
+
{
|
|
326
|
+
"telemetry": null
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## 15. Inspect persisted artifact metadata
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
wattson session artifacts session-123
|
|
334
|
+
wattson --json session artifacts session-123
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Use this when you want artifact state/metadata without pulling inline FIT base64 content.
|
|
338
|
+
|
|
339
|
+
## 16. Fetch the deterministic FIT export
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
wattson session fit session-123
|
|
343
|
+
wattson --json session fit session-123 > /tmp/session-fit.json
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Important:
|
|
347
|
+
- `session fit` returns inline `contentBase64`
|
|
348
|
+
- `session artifacts` is usually the better inspection command when you only need status/metadata
|
|
349
|
+
|
|
350
|
+
## 17. Enqueue or retry Strava sync
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
wattson session strava-sync session-123
|
|
354
|
+
wattson --json session strava-sync session-123
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Important:
|
|
358
|
+
- this corresponds to an async backend operation
|
|
359
|
+
- the underlying HTTP route returns `202 Accepted`
|
|
360
|
+
- use `session artifacts`, `session fit`, or later session reads to inspect the resulting state
|
|
361
|
+
|
|
362
|
+
## A practical agent loop
|
|
363
|
+
|
|
364
|
+
A practical agent should usually do this:
|
|
365
|
+
|
|
366
|
+
1. interpret the user request
|
|
367
|
+
2. choose the smallest structured planner action that matches it
|
|
368
|
+
3. keep natural language as context / rationale, not as the API contract itself
|
|
369
|
+
4. validate before saving when changing workout structure
|
|
370
|
+
5. ingest final session completion once the ride is done
|
|
371
|
+
6. inspect telemetry / artifacts / FIT / Strava sync state after completion when durability matters
|
|
372
|
+
7. use bounded live adaptation ops during an active session
|
|
373
|
+
|
|
374
|
+
The planner API should stay structured.
|
|
375
|
+
The agent should do the interpretation.
|