Lifecycle hooks
Hooks are blocks of logic that run at moments in a workout's lifecycle. They read what happened — reps, weights, whether you hit your targets — and write back training-max and state changes that make a program adapt from session to session.
This is the complete reference. For a gentler introduction, start with Progressions & hooks.
The two hook kinds
| Hook | Fires | Typical job |
|---|---|---|
on_set | mid-workout, as you finish each set | Pre-fill the next set (autoregulation) |
on_complete | after a unit (exercise / day / week) is fully done | Decide progression — bump a TM, update state |
on_set is meant to run during a workout; on_complete runs when you complete the session.
on_setis parsed and validated but not yet evaluated at runtime. The compiler acceptson_setblocks (inline or supplied by a macro), but the app currently drives behavior fromon_completeonly —next_set_weight/next_set_repsare not applied yet. Writeon_setlogic if you like, but don't depend on it taking effect today.
Hook scopes
on_complete can be attached at three scopes. They are evaluated in this order when a session
completes:
- Exercise — once per completed exercise block.
- Day — once, after every exercise in the day.
- Week — once, only on the session that completes the last day of the week.
State writes thread forward: a state change made by an exercise hook is visible to the day hook,
and the day's writes are visible to the week hook.
Program-level
on_completeis parsed but not evaluated. The grammar accepts a top-levelon_completeblock, but the app does not run it at workout completion — don't rely on it. Use exercise, day, or week scope instead.
Variables by scope
Every variable is a number (booleans are 1/0). Weights are in the lifter's own unit and are
never converted. Which variables exist depends on the scope, because day- and week-level hooks have
no single "current set" to report.
Exercise on_complete
| Variable | Meaning |
|---|---|
completed | 1 if every working set hit its target |
completed_reps | Reps logged on the final working set |
total_reps | Total reps across all working sets |
target_reps | Target reps (a range's minimum) |
current_weight | Weight on the final working set |
set_index / set_count | Index / count of working sets |
failed_sets | Working sets that fell short of target |
rpe_logged / rir_logged | RPE / RIR logged on the final set (0 if none) |
is_amrap | 1 if the final set was AMRAP |
is_pr | 1 if this exercise set a PR this session |
estimated_1rm | Estimated 1RM from the heaviest set |
week_index / day_index | 1-based position in the program |
session_count | Total sessions completed in this enrollment |
sum_volume | Weight × reps across this exercise's sets |
unit_is_metric | 1 for kg, 0 for lbs |
Plus tm.self (this exercise's training max), tm.<exercise> (any TM in the tm { } block), and
state.<name> (any declared state variable).
Day on_complete
Day hooks see only program-context variables — no per-set data, and no tm.self:
week_index, day_index, session_count, day_volume (weight × reps across the whole day),
unit_is_metric, and state.<name>.
Week on_complete
Everything a day hook sees, plus week_volume (weight × reps across every session in the week).
on_set outputs
on_set is intended to run mid-workout and expose the in-progress set context. The two variables it
may write are the only way to influence the next set (note the runtime caveat above — these
outputs aren't applied yet):
| Writable output | Effect |
|---|---|
next_set_weight | Pre-fills the weight of the next set |
next_set_reps | Pre-fills the target reps of the next set |
What a hook may write
| Target | Allowed in | Notes |
|---|---|---|
tm.self | exercise on_complete | This exercise's training max |
tm.<exercise> | exercise on_complete | Any TM declared in tm { } |
state.<name> | any scope | Any variable declared in state { } |
next_set_weight / next_set_reps | on_set only | Pre-fill the next set |
Anything else is rejected at compile time. In particular, deep paths like state.x.y and reserved
keys (__proto__, constructor, prototype, hasOwnProperty) are forbidden.
Runtime caveats
These rules are enforced when the app evaluates your hooks. A hook can be syntactically valid yet silently do nothing if it ignores them.
- Day and week hooks cannot change a training max.
tm.selfonly has meaning in an exercise's context, so anytmwrite from a day- or week-level hook is ignored (with a warning). Usestateto accumulate across a day or week, then act at the exercise level. - Training-max changes are capped at ±20% of the current value in a single evaluation. A larger requested change is clamped to the cap.
- Big TM moves aren't applied silently. A TM increase within one normal step (≤ 5 kg / ≤ 10 lb) applies automatically. A larger increase, or any decrease, is held for you to confirm on the workout summary rather than applied behind your back.
- A broken hook never breaks your workout. If a hook fails to parse or is rejected, evaluation degrades to "no mutation" — your session completes normally with no changes.
Deload weeks
On a deload week, on_complete hooks are suppressed by default so a back-off week doesn't
trigger progression. on_set still runs. Re-enable on_complete with hooks: enabled at the week
(or day) level:
A real example
This is the 5/3/1 main-lift progression: on the week-3 AMRAP top set, if you hit at least one rep, bump the training max by the unit-correct amount.
Next, see how to package logic like this into a reusable rule on the progression macros page, or skim the language reference for the full grammar.