Loadstar

One portable JSON file for a whole training block: the plan written by your coach (human or AI) and the log your app appends. It travels both ways.

GymSync Block Format v2 (Specification)

Status: draft for implementation · 2026-07-10 · schema: gymsync-block-v2.schema.json

One JSON file = one training block (4–16 weeks). The file is both the plan

(written by a coach: human or AI) and the log (appended by the athlete's

app). It travels in both directions: load it into GymSync to train, hand it back

to the coach to review and to generate the next block.

Design principles

1. Plan ≠ log. sessions (with goals, nutrition, phases, exercises)

is the immutable intent; log is append-only fact. Apps MUST NOT rewrite the

plan to record results: that is what makes "planned vs performed" analysis

possible. Mid-block plan edits are recorded per affected logged session in

log.sessions[].amendments.

2. Stable ids, display names. Exercises are defined once in the exercises

catalog and referenced by exerciseId. PR tracking, muscle volume and

cross-block comparisons key on ids, never on names. work[].variant

("Heavy", "Volume", "drop set") is display-only.

3. Cardio is part of the plan. Runs, rides, swims and HIIT are sessions with

type + cardio targets: one calendar, one adherence metric, one load model.

4. Everything needed to coach is in the file: goals with baselines and test

sessions, nutrition targets (incl. training-day carb delta), phase structure.

5. Extensible, strictly. Unknown keys are rejected except x-* extension

fields, which tools SHOULD preserve on read-modify-write.

File conventions

shared between the app and the coach (GymSync uses …/Gym/blocks/).

Semantics that tools must agree on

Volume (tonnage)

set tonnage = reps × load × armFactor, where armFactor = 2 when the

exercise has perArm or dualDB, else 1.

stages: style: "perSet" → the set's stage; style: "dropRounds" → the SUM

of all stages (one "set" = one full drop round).

(kg then means added external load). Apps SHOULD capture bodyWeightKg

at session start so history never drifts as the athlete's weight changes.

Session matching & completion

A plan session is done when a log.sessions[] entry references it via

planSessionId and the entry is not skipped: true (see Skipped

sessions). Completion is scoped to this file (a new block may reuse ids)

like W1D1 freely. Unplanned workouts log with type and no planSessionId.

Estimated 1RM

Tools SHOULD use Epley (kg × (1 + reps/30)) when displaying e1RM trends so

numbers agree across apps and coaches.

RPE

0–10 scale in practice 5–10; 8 = two reps in reserve. Apps SHOULD prompt for

the main lift's RPE at session finish if not captured per set.

Wearable data

watch (strength) and cardioResult (cardio) carry the summary a wearable

recorded: duration, avg/max HR, kcal, and zoneMin (minutes in 5 HR zones)

(zone bounds are the athlete's own; publish them in x-hrZones on meta if

tools need to agree).

Structured intervals (additive, v2)

cardio.intervals describes treadmill/track interval structure as an ordered

list. Each item is either a named phase (phase + sec or meters) or a

repeated block (repeat + work/rest legs). Every phase/leg SHOULD carry

pace ("m:ss" min/km): with pace set, duration is derivable for distance

phases and distance for timed phases, a watch app can then run the whole

session hands-free (countdowns, belt-speed hints 3600/paceSec, honest

distance from the plan). The rest after the final rep is implicitly dropped.

```json

"cardio": { "kind": "interval", "hr": [150, 172], "intervals": [

{ "phase": "rozgrzewka", "sec": 600, "pace": "5:40" },

{ "repeat": 6, "work": { "sec": 180, "pace": "4:40" },

"rest": { "sec": 120, "pace": "7:00" } },

{ "phase": "cooldown", "sec": 300, "pace": "6:30" }

] }

```

Meal plan (nutrition v2, additive)

nutrition grew from flat targets into a full meal plan (July 2026); the old

trainingDayCarbsDelta is DEPRECATED (ignored when dayTypes present).

target(day) = weight × weeklyBudget / Σ(week weights) (days differ,)

the weekly mean holds the budget (carb-cycling emerges). Day type resolves

from the plan's sessions (strength wins; else the cardio kind; else rest)

with fallback families (tempo→interval, zone2/recovery/technique/run→easy,

deload→rest, test→strength).

product.name/product.recipeId); flex = water-filled to the day target

within portion bounds. intra slots (during-session fueling) belong on

long-endurance days. Per-date exceptions live in dayOverrides.

(app re-injects on every save) and the delivery vehicle for coach proposals:

apps MERGE by id on import (upsert, never delete); coach owns tr-* ids.

Ingredients carry per-100 snapshots; liquids use unit: "ml" (label values

1:1), optional density (g/ml) for future conversions. tags hold TWO

orthogonal axes in one array: slot × session type; empty axis = universal;

match rule: slot(S)∈axis1 AND (axis2 empty OR canon(dayType)∈axis2).

is the athlete's append-only FACT: same barrier as log: coach revisions

must never touch it, and apps must preserve the local copy when applying

a revision.

Lifecycle

```

coach (AI/human) ──plan──▶ file ──load──▶ app (train, log appends)

▲ │

└───────────── review / next block ◀─────┘

```

Mid-block revision (coach edit)

The file may also travel back mid-block: the athlete exports the current

block (log included), the coach revises FUTURE sessions (swap exercises,)

trim volume after a weak week, even adjust goals (and the athlete loads the)

same file again. Apps replace the plan wholesale and reconcile against local

history, so completed work survives as long as the revision:

Hand-written log entries may use a bare date for started/finished

("2026-08-10"): apps anchor them at noon local time.

Skipped sessions

A deliberately skipped session is logged as

{"planSessionId": "W2D1", "started": "<date>", "skipped": true}.

A session counts as completed only when a log entry references its id

AND is not skipped: adherence tools must not count skips as done.

For a coach, skipped entries are the signal to revise the remaining weeks.

Cardio-only blocks

A block with no strength work at all (pure running base, tri prep without

gym access) is valid: keep the exercises key with an empty array and use

cardioTime/cardioDistance/measurement goals instead of lift goals.

Closing a block (optional): the app MAY write log.dailiesSnapshot (daily

kcal/sleep/HRV/steps/TRIMP) as an archive before the file is retired to history.

Versioning

gymsyncBlock: 2 is required. Breaking changes bump the integer; additive

changes extend the schema without bumping. v1 (gymsyncPlan: 1) files convert

mechanically with convert_v1_to_v2.py.

v3 outlook: if OpenWeight (openweight.dev) gains real adoption, v3 will consider

converging with it: either log-level interop (gymsync:* extension fields) or

upstreaming the "program as contract" concepts. Until then GymSync evolves independently.

Tooling in this directory

phase inference).

run-base-sample (cardio-only: empty catalog, structured intervals),

block3-100kg-bench-2026 (a real production block).