Hooks & macros reference
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. A progression macro packages hook logic (and
optionally a set scheme) into a reusable rule you attach to as many exercises as you like with
using.
This is the complete reference. For a gentler introduction, start with Progressions & hooks.
Two flavours of macro
| Flavour | Body contains | What using does |
|---|---|---|
| Set-scheme | set lines (5 @%90, amrap @%100) | Expands into the exercise's sets |
| Hook | an on_complete / on_set block | Becomes the exercise's progression hook |
A macro may contain both. The built-in macros (below) are set-scheme only — they lay out reps and percentages but do not progress the training max on their own. Pair them with a hook (inline or your own macro) to add progression.
A macro's
on_completehook drives progression today; anon_sethook is parsed and validated but not yet evaluated at runtime (see runtime caveats below).
Attach with using:
Parameters
A macro declares parameters in parentheses. Each may have an optional : type annotation
(number or weight) and an optional = default:
progression "ramp" (increment = 2.5, bump: weight = 2.5kg, target: weight) { ... }
- A
weightdefault carries a unit (2.5kg); the type is inferred from a weight default. - An omitted parameter falls back to its default. An omitted parameter with no default is a compile error.
- Parameter names may shadow built-in variables — that's intentional, so a macro can name a
parameter
target_repsor similar without a conflict. They can't shadow a language keyword (week,state,using, and so on).
Value- vs reference-binding
How you bind an argument at the using site decides whether the macro can write to it — this is
what makes a macro stateful.
| Binding | Example | Inside the macro |
|---|---|---|
| Value (literal) | using "ramp" (bump: 5kg) | Read-only. Assigning to it is a compile error. |
| Reference (state) | using "ramp" (load: state.row_weight) | Writable. Assignments write through to the referenced state variable. |
Barbell Row {
8-12 @state.row_weight
using "double_prog" (load: state.row_weight, top: 12)
}
A set-scheme macro takes literal arguments only — passing a state reference to one is an error, because there's no number to expand into a set.
Override & precedence rules
- A
usingmacro that supplies sets makes the exercise's own explicit sets ignored (with a warning) — write the sets in one place. - An inline
on_complete/on_sethook on the exercise overrides a hook the macro would supply. - A script-level
progression "name"with the same name as a built-in overrides the built-in. - An unknown macro name is a warning; the exercise keeps its own sets.
Built-in macros
Four set-scheme macros ship with every program — use them by name without defining anything. Each
declares an increment parameter for convention, but remember these are set layouts, not
progression logic:
| Name | Sets it lays out | Defaults |
|---|---|---|
lp | amrap @%100 | increment = 5 |
lp_deload | 5 / 3 / amrap, all at @%deload_pct | increment = 5, deload_pct = 90 |
dp | 5 @%90, 3 @%95, amrap @%100 | increment = 2.5 |
wave_lp | 5 @%65, 5 @%75, 5 @%85 | increment = 5 |
Bench Press {
using "dp"
}
Built-in functions
Available inside any hook body (and therefore any macro hook):
| Function | Returns |
|---|---|
round(x, n) | x rounded to the nearest multiple of n |
floor(x) / ceil(x) | Round down / up to a whole number |
min(a, b) / max(a, b) | Smaller / larger of the two |
clamp(x, lo, hi) | x constrained to [lo, hi] |
pct_of(base, pct) | base × pct (pct as a fraction) |
rpe_to_pct(reps, rpe) | %1RM for a reps/RPE pair (RTS table) |
pick(cond, a, b) | a if cond is non-zero, else b |
Unit-aware increments
Weights in ReptoLang are raw numbers labelled with a unit — nothing ever converts kg ↔ lbs. To bump
by the right amount for either lifter, write a unit pair { <kg>, <lbs> }:
{ Akg, Blbs } is shorthand for pick(unit_is_metric, A, B). It needs exactly one kg and one
lbs slot (order doesn't matter), each a non-negative weight literal — a leading - is a parse
error, so write a subtraction (tm.self -= { 2.5kg, 5lbs }) instead.
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 |
amrap_reps | Reps on the heaviest AMRAP working set (0 if none) — distinct from completed_reps for schemes like nSuns whose top AMRAP set isn't the final one |
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.
Type rules
Hook bodies are type-checked at compile time, distinguishing plain numbers from unit-carrying weights:
- Adding or subtracting a weight and a unit-less number is a compile error (
tm.self + 5— writetm.self + 5kg, or use a unit pair). - Multiplying a weight by a weight is a compile error (
kg²has no meaning). - Dividing a plain number by a weight is a compile error.
- Assigning a unit-less number to a weight target (
tm.self,tm.<exercise>) is a compile error. - Comparing a weight to a plain number produces a warning, not an error.
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.
For the full grammar, see the language reference.