A training app that tells you to squat 102.5 kg is only useful if you can find out why. Without the rule behind it, a recommendation is a number you either trust or ignore, and most lifters end up ignoring it by about week three.
So here is the whole decision, written out. Not a description of the philosophy: the actual rule, the actual thresholds, and the order the checks run in. You should be able to predict what IronLedger will suggest next week before next week arrives. If you can't, the feature has failed at its job.
Why a rule instead of a model?
The obvious thing to build in 2026 would be an AI coach. Feed the log to a model, ask it what to do next, render the answer. Several apps do exactly this.
We didn't, for one reason: you cannot audit it. If a model tells you to squat 102.5 kg and you ask why, the honest answer is that a very large function returned that number. You cannot check it. You cannot predict it. And when it produces something odd on a bad week, you have no way to tell whether it understood something you didn't or simply made a mistake.
A rule has none of that ambiguity. You can do the arithmetic in your head while you load the bar. That matters more than it sounds: a rule you can predict is a rule you can plan around, and a rule you can plan around is one you will actually follow when the session is hard and the plates are already on.
The tradeoff is real and worth stating. A deterministic rule will not notice that you've had three bad weeks in a row because of something outside the gym. It doesn't infer. It applies what it's told to a history you gave it, and it tells you which part it used.
The rule, and where it runs
Live load recommendations run on IronLedger's five load-and-go programs: Starting Strength, 5/3/1 Boring But Big, PHUL, Push/Pull/Legs Strength Bias and Upper/Lower Powerbuilding. For each exercise in those programs, the app shows the recommended load, where it came from, and a one-sentence reason. Custom and imported programs don't get live recommendations; more on that below.
1. Hit your reps at an easy effort, add weight
The blunt part, and correct for most of a beginner or intermediate block. If you hit every prescribed rep last session and the effort was easy, the rule adds 2.5 in your unit (kg or lb) to last session's load.
It's blunt on purpose. For a lifter who is genuinely adding weight every session, anything cleverer is noise.
2. Effort decides when to hold
The same rule also stops one hard day compounding into a missed week. It reads the effort you reported against two thresholds and decides whether to advance or hold.
The thresholds:
| Last session reported | What happens next |
|---|---|
| All prescribed reps, and RPE 7 or below, 3+ RIR, or no effort logged | Increase by 2.5 |
| RPE 8 | Hold the load |
| RPE 9 or above, or 0 RIR | Hold the load |
| Prescribed reps missed | Hold the load |
Two details worth knowing. When several sets are logged, the engine takes the highest RPE and the lowest RIR across them, so the hardest set governs the decision rather than an average that would flatter a session. And if you logged no effort values at all but hit the prescribed reps, it treats that as a clean session and increases.
3. 5/3/1 and Challenge to Change work from a training max
5/3/1 and Challenge to Change don't react to last session. Their loads are percentages of your training max, which is 90% of a tested or estimated 1RM by default, and you can adjust it. The Why sheet shows the arithmetic behind a load: the percentage, the training max and the rounding.
When the 1RM is estimated, IronLedger uses the Epley formula:
estimated 1RM = weight × (1 + reps ÷ 30)
100 kg × 5 reps → 100 × (1 + 5/30) = 116.7 kg
training max → 116.7 × 0.9 ≈ 105 kg
Estimated 1RM is a trend signal, not a prediction of what you'd hit on a maximal attempt. If the number jumps because you logged a heavy triple for the first time in a month, that jump is arithmetic, not adaptation.
If you're building your own block, the bigger choice is whether it runs a linear structure or a daily undulating (DUP) rotation with a different target most sessions. See DUP vs linear periodization for what the research says about the strength difference between them.
The order the checks run in
The checks don't compete. The engine runs them in a fixed order and stops at the first match:
- Manual override. If you change a recommended weight yourself, that wins for the session. Always.
- Training max. On 5/3/1, the load is a percentage of your training max, and last session doesn't move it.
- Otherwise, the rule above.
Inside step 3 the checks are also ordered: no history yet (use the program default), then reps missed, then effort too high, then effort low enough to advance. Anything that doesn't match holds the load. Holding is the default outcome, which is deliberate: when the engine is unsure, it should not add weight.
Reading the reason line
Every recommendation carries a sentence that stands alone. If you read only that line, you should be able to reconstruct the decision. It names two things:
- What happened last time, as logged values rather than a judgement
- What the engine did about it: increase, hold, or use the program default
Next to it, a badge shows where the load came from, such as your latest history or the program default, and the Why sheet shows the arithmetic: last session's load plus the step, or a percentage of your training max.
In practice that reads like "Last session RPE 9 at 100 kg, holding load" or "Last session hit all prescribed reps, increasing squat by 2.5 kg." No hedging, no personality, no encouragement. It's a log entry, not a coach.
Overriding it
The engine is advice. If your judgement disagrees, change the number.
When you do, IronLedger marks that recommendation as a manual override for the session and still shows its own suggested numbers on the card. That's not a nag. The next recommendation then works from what you actually lifted.
One thing the app will never do is quietly rewrite a set you logged. A logged set is a record of something that happened.
How we know it behaves
The engine has a test suite covering the rule paths, and it runs on every commit. That's a low bar and worth being honest about, but determinism is what makes it possible at all: the same input always produces the same target, so the behaviour can be pinned to a specific expected number rather than a range.
That property is the whole argument for building it this way. You cannot write a test like that against a model.
Frequently asked questions
No. The progression engine is deterministic code, not a large language model. The same logged sets always produce the same target, and every recommendation shows where it came from and the reason behind it.
The short version
One rule on five programs, published thresholds, a stated order, a reason on every recommendation, and an override that the engine respects rather than argues with.
You don't have to trust the app. You can check it. That was the point.
Read more on progressive overload tracking, RPE versus percentage-based training, and how to structure a deload.

