Personalize this lesson
Adapt explanations and teaching visuals to your background and preferred voice.
A coding agent passes eight of ten held-out tasks. Is its success rate above 70%? The observed rate is 80%, but ten tasks leave room for luck. We need to distinguish a promising estimate from strong evidence that the agent clears the threshold.
Probability for Machine Learning introduced base rates and conditioning. Bayesian inference applies the same reasoning to an unknown model parameter: state which values were plausible before the evaluation, then update those beliefs using the results. The answer is a distribution of plausible success rates, not just one adjusted score.[1]
We'll use those same eight passes and two failures throughout. Start with a mild preference for middle success rates, then change that preference to see how much the conclusion depends on it.
Formulating the unknown success parameter
Let mean the probability that this agent passes a randomly drawn task from the intended deployment population. Pin the agent's model, prompt, tools, retry budget, and pass criterion. One observation is one task evaluated under that fixed policy.
Our simple model treats the outcomes as independent conditional on , with the same success probability for each draw. That means: if we knew , knowing one result wouldn't change the probability of another. Tasks can differ in difficulty, but their sampling process must stay the same. Ten handpicked easy tasks won't support a claim about a different production mix.
| Quantity | Question it answers | Worked value |
|---|---|---|
| Prior | Which rates were plausible before this eval? | A broad distribution centered at 0.5 |
| Observations | What actually happened? | 8 passes, 2 failures |
| Likelihood | How probable are the observed results at a candidate rate? | Compare eight passes out of ten at and |
| Posterior | Which rates are plausible after the update? | More weight near high success rates, with uncertainty remaining |
The direction of conditioning matters. A likelihood asks about the data given a proposed rate. A posterior asks about the rate given the data. Bayes' rule connects them; they aren't interchangeable.
Binomial likelihood scores candidate success rates
At a candidate rate of 0.8, each pass contributes a factor of 0.8 and each failure contributes 0.2. One particular sequence of eight passes and two failures has probability . There are 45 possible positions for the two failures, so the probability of exactly eight passes, regardless of order, is 45 times that value.
Python's comb(10, 8) counts the 45 unique arrangements of eight passes across ten trials. Evaluating this binomial formula across candidate rates reveals how sharply the observed run separates hypotheses:
1from math import comb
2
3for rate in (0.2, 0.5, 0.8):
4 likelihood = comb(10, 8) * rate**8 * (1 - rate)**2
5 print(f"rate={rate:.1f}: P(exactly 8 passes)={likelihood:.6f}")1rate=0.2: P(exactly 8 passes)=0.000074
2rate=0.5: P(exactly 8 passes)=0.043945
3rate=0.8: P(exactly 8 passes)=0.301990The eight-pass result is about 6.9 times as likely at rate 0.8 as at rate 0.5. That favors 0.8 relative to 0.5, but it doesn't mean there's a 30.2% posterior probability that the rate is 0.8. We haven't combined the evidence with a prior.
For a general candidate rate, the likelihood is . The factor 45 is the same for every candidate, so it cancels when we normalize the posterior. We can therefore work with a likelihood proportional to . The symbol means "equal up to a constant factor."
Beta distributions quantify prior beliefs
A beta distribution describes uncertainty about a probability between 0 and 1. Its two positive shape parameters, and , control where that uncertainty sits. We'll choose Beta(2, 2): it favors middle rates over near-zero or near-one rates, without ruling out a broad range.
This is an explicit modeling choice, not a claim that we observed two historical passes and two failures. A uniform Beta(1, 1) prior would give equal density to all rates. Increasing both parameters equally concentrates the distribution around 0.5. These alternatives make it possible to test how much the prior matters.
For a continuous rate, probability belongs to a range of values, represented by area under a density curve. The height at one point isn't a probability and can exceed 1. Beta(2, 2) has density ; the constant 6 makes the total area from 0 to 1 equal to 1.[2]
Bayes' rule multiplies that prior shape by the likelihood, then rescales the result so its total area is 1:
For our run, multiplying by gives . A beta density has the shape , so the new exponents identify Beta(10, 4). The prior's exponents and the data's exponents add; the observations don't replace the prior.[3]

Beta-binomial conjugacy produces an exact posterior
Now the arithmetic has a reason behind it: and . A beta prior combined with a binomial count likelihood stays in the beta family. This makes beta a conjugate prior for that likelihood. For any observed data containing passes and failures:
Here and are the prior parameters, counts observed passes, and counts observed failures. Substitute the actual numbers:
Posterior expectation is given by:
Observed alone, the empirical pass fraction is . Before seeing any runs, the prior mean was . After the update, the posterior mean resolves to an exact weighted average of those two quantities:
Here the prior holds weight 4 while the empirical runs contribute weight 10. These weights explain the gravitational pull toward 0.5; they don't mean anyone actually ran four historical benchmark tasks.
The next cell repeats that arithmetic and checks the parameter totals before printing the updated rate.
1prior_success = 2
2prior_failure = 2
3observed_pass = 8
4observed_fail = 2
5
6posterior_success = prior_success + observed_pass
7posterior_failure = prior_failure + observed_fail
8posterior_mean = posterior_success / (posterior_success + posterior_failure)
9
10assert (posterior_success, posterior_failure) == (10, 4)
11print(f"observed rate: {observed_pass / (observed_pass + observed_fail):.3f}")
12print(f"posterior: Beta({posterior_success}, {posterior_failure})")
13print(f"next-task pass probability: {posterior_mean:.3f}")1observed rate: 0.800
2posterior: Beta(10, 4)
3next-task pass probability: 0.714Forecasting the next run with the posterior predictive
If the next task is comparable, the posterior predictive probability gives a forecast before it runs. To forecast the outcome of the eleventh task, average each candidate success rate weighted by its posterior credibility. At rate 0.6, the next task has pass probability 0.6; at rate 0.8, it has probability 0.8. Integrating over the entire Beta(10, 4) distribution yields an expected future pass probability equal to the posterior mean, .[3]
This reflects uncertainty about a future outcome, not a claim that is exactly 0.714. If the next task passes, the posterior becomes Beta(11, 4), and the following task's forecast rises to . Sequential updates and one combined update give the same parameters when they use the same observations and model.
Why does the posterior mean equal 10/14 instead of the observed pass rate 8/10?
Answer
The Beta(2,2) prior contributes parameter weights of 2 and 2. Combining them with eight passes and two failures yields Beta(10,4), whose mean is 10 divided by 14. Those prior weights are assumptions, not extra observed tasks.
Prior sensitivity: when assumptions outweigh small samples
Two engineers can see the same ten outcomes and reach different posterior means because they chose different priors. Hold the observations fixed and vary only the starting assumption.
The next cell reuses the pass and failure counts and compares a weak Beta(1, 1) prior, a moderate Beta(2, 2) prior, and a strong Beta(20, 20) prior. Predict what should happen before reading the numbers: the strong prior should stay closer to 0.5.
1for success_count, failure_count in [(1, 1), (2, 2), (20, 20)]:
2 updated_success = success_count + observed_pass
3 updated_total = success_count + failure_count + observed_pass + observed_fail
4 print(
5 f"Beta({success_count:>2}, {failure_count:>2}) -> "
6 f"posterior mean {updated_success / updated_total:.3f}"
7 )1Beta( 1, 1) -> posterior mean 0.750
2Beta( 2, 2) -> posterior mean 0.714
3Beta(20, 20) -> posterior mean 0.560Beta(20, 20) gives its starting mean a weight of 40, compared with 10 for the new data. The updated mean is therefore . That strength needs justification, such as substantial evidence that genuinely applies to this agent and task population.
Relative to a uniform Beta(1, 1) baseline, reaching Beta(20, 20) would require 19 historical passes and 19 historical failures, not 40 observed tasks. Parameter weights function as bookkeeping; they aren't provenance records. Keep those two facts separate.
| Prior | Prior parameter weight | Posterior mean | Interpretation |
|---|---|---|---|
| Beta(1, 1) | 2 | 0.750 | Weak symmetric starting assumption |
| Beta(2, 2) | 4 | 0.714 | Mild pull toward 0.5 |
| Beta(20, 20) | 40 | 0.560 | Strong prior dominates ten tasks |
Report several defensible priors when the sample is small. If the deployment decision flips between them, collect more representative evidence instead of quietly choosing the prior that makes the preferred outcome win.
An engineer claims Beta(20,20) proves that 40 comparable historical tasks were observed. What information is missing?
Answer
The prior has total parameter weight 40, but that weight isn't automatically 40 observed tasks. Relative to a uniform Beta(1,1) baseline, the same prior could result from 19 historical passes and 19 historical failures. The earlier baseline and the historical evidence's provenance must both be disclosed.
Credible intervals and tail probabilities versus point averages
A mean of 0.714 leaves our original question unanswered: how much evidence says the rate exceeds 0.7? Beta(10, 4) assigns probability both below and above that threshold.
A 90% equal-tailed credible interval leaves 5% of the posterior area below its lower endpoint and 5% above its upper endpoint. The remaining 90% lies between them. These endpoints are the 5th and 95th quantiles, or percentile cutoffs, of the posterior.
SciPy's beta.ppf returns those cutoffs. Its beta.sf(0.7, a, b) returns the area above 0.7, which directly answers our threshold question.[2] Use the same Beta(10, 4) posterior for both calculations:
1from scipy.stats import beta
2
3a, b = 10, 4
4lower, upper = beta.ppf([0.05, 0.95], a, b)
5prob_above_threshold = beta.sf(0.7, a, b)
6
7print(f"90% credible interval: [{lower:.3f}, {upper:.3f}]")
8print(f"P(rate > 0.7 | data): {prob_above_threshold:.3f}")
9assert abs((beta.cdf(upper, a, b) - beta.cdf(lower, a, b)) - 0.9) < 1e-10190% credible interval: [0.505, 0.887]
2P(rate > 0.7 | data): 0.579Calculating the quantiles yields an interval of [0.505, 0.887], while the posterior probability above 0.7 is only 0.579. Having an expected value of 0.714 doesn't mean you're confident the agent clears 0.70; in fact, there's a 42.1% chance its true rate falls below your release bar!
Crucially, this interval reflects parameter uncertainty under the chosen prior and binomial model. Tasks themselves don't yield fractional outputs: each run either succeeds or fails. Frequentist procedures interpret intervals differently, evaluating long-run coverage under repeated sampling. Subsequent lessons explore those distinctions in detail.
Another summary is the posterior standard deviation, which measures spread around the mean. Its square, the variance, has a closed form for Beta():
At Beta(10, 4), the variance is , and its square root is about 0.117. Use beta quantiles for the interval above; a shortcut such as mean plus or minus two standard deviations needn't give the intended probability, especially near 0 or 1.
The posterior mean exceeds 0.7. Does the posterior assign at least 90% probability to rates above 0.7?
Answer
No. The mean is 0.714, but the posterior area above 0.7 is only about 0.579. The average and the probability of clearing a threshold answer different questions.
Sample size scaling: comparing 8/10 against 80/100
Hold the Beta(2, 2) prior fixed and compare two alternative sample sizes: eight passes out of ten and eighty passes out of one hundred. Both observed rates are 0.8. The larger run yields Beta(82, 22), not Beta(90, 24): we're replacing the ten-task example with a hundred-task example, not adding the runs together.
The next independent cell computes both means, standard deviations, and equal-tailed intervals. Predict which interval will be narrower before running it:
1from math import sqrt
2from scipy.stats import beta
3
4for observed_pass, observed_fail in [(8, 2), (80, 20)]:
5 a, b = 2 + observed_pass, 2 + observed_fail
6 total = a + b
7 mean = a / total
8 variance = a * b / (total**2 * (total + 1))
9 lower, upper = beta.ppf([0.05, 0.95], a, b)
10 print(f"Beta({a}, {b}): mean={mean:.3f}, sd={sqrt(variance):.3f}")
11 print(f"90% interval: [{lower:.3f}, {upper:.3f}]")
12 print(f"P(rate > 0.7 | data): {beta.sf(0.7, a, b):.3f}")1Beta(10, 4): mean=0.714, sd=0.117
290% interval: [0.505, 0.887]
3P(rate > 0.7 | data): 0.579
4Beta(82, 22): mean=0.788, sd=0.040
590% interval: [0.720, 0.851]
6P(rate > 0.7 | data): 0.981With one hundred tasks, the posterior mean moves to 0.788, the spread tightens dramatically, and the 90% credible interval contracts to [0.720, 0.851]. Posterior tail probability above 0.7 jumps from 57.9% to 98.1%. That contrast shows why sample size transforms raw fractions into actionable decisions: identical 80% empirical rates deliver completely opposite degrees of certainty.

More data doesn't remove randomness from individual future tasks. Even if we learned that was exactly 0.8, a new task could still fail. It also doesn't remove uncertainty caused by a wrong model or an unrepresentative sample.
Posterior dispersion versus empirical calibration
Posterior spread and calibration answer different questions. The posterior describes uncertainty about a success rate under a chosen model. Calibration asks whether forecasts line up with later outcomes: among many comparable tasks assigned a pass probability near 0.7, do about 70% pass?
A frozen 0.714 forecast can't establish calibration from the same ten tasks that produced it. Save forecasts before later outcomes arrive. Across enough predictions, group similar probabilities and compare each group's average forecast with its observed pass fraction.[4] With only one frozen forecast, this amounts to comparing that forecast with the pass fraction on a fresh batch.
Small batches fluctuate, so one mismatch isn't proof of a bad model. Persistent discrepancies deserve investigation: did the task mix shift, were retries counted as new tasks, or did the agent's policy change? More data can narrow a wrong model's posterior without making its predictions reliable.
Sampling failure modes that corrupt the posterior
The arithmetic can be correct while the inference is wrong.
| Failure | Visible symptom | Repair |
|---|---|---|
| Repeated retries of one task | Ten rows claim to represent ten independent tasks | Group retries by underlying task and sample distinct tasks |
| Easy benchmark slice | Posterior looks strong while hard production tasks fail | Stratify by task family and compare deployment traffic |
| Historical prior from another agent | Strong prior hides a real regression | Record prior provenance and run sensitivity checks |
| Hidden prompt changes | Outcomes combine incompatible policies | Pin model, prompt, tools, and task definitions |
| Selective release reporting | Only a favorable stopping point or chosen agent is disclosed | Record every evaluated agent, stopping rule, and release decision |
Keep the collection and reporting process visible. Omitted runs, selected agents, or an undisclosed change of target population can make the reported analysis answer a different question from the one a reader assumes. Updating can't turn duplicated or biased rows into representative observations.
A model passes eight out of ten tasks, but all ten rows are retries of the same prompt. Why is Beta(10,4) misleading?
Answer
The intended parameter describes new tasks from the deployment population. Ten retries of one prompt mainly describe that prompt. Even if retry randomness is independent, the prompt wasn't sampled ten times independently from that population. A beta update might model that prompt's retry success rate, but it doesn't establish the broader task success rate.
Production release gates with posterior decision rules
For this exercise, choose the rule before seeing results: increase traffic only if the posterior probability that is at least 95%. This is an illustrative evidence threshold, not a universal release policy. Real decisions also depend on the cost of failures, traffic exposure, and other safety checks.
Under Beta(2, 2), the ten-task result doesn't pass this rule: 0.579 < 0.95. The hundred-task result does: 0.981 > 0.95, provided its tasks and policy satisfy the model assumptions. Neither decision follows from the raw 80% rate alone.
Try these changes before reading the answers:
- One more comparable task fails after the original eight passes and two failures. What are the new posterior and next-task forecast?
- Apply the 95% evidence rule to the original ten outcomes with Beta(1, 1) and Beta(20, 20) priors. Use
beta.sf(0.7, a, b), not just the mean. Does either pass? - Could the hundred-task result justify the same claim if every task came from an unusually easy benchmark family?
Decision and sensitivity analysis checks
- Add the failure once: Beta(10, 5), with forecast . Don't add the original ten observations again when starting from their posterior.
- Posteriors update to Beta(9, 3) and Beta(28, 22). Their probabilities above 0.7 evaluate to 0.687 and 0.019, respectively. Neither meets the 0.95 requirement. Although the estimates differ, all three priors produce the exact same rejection decision under this rule.
- No. That posterior describes the sampled task family. A narrow interval doesn't correct the mismatch with deployment traffic; sample from the intended population or explicitly model and weight its task families.
Report the observations, prior justification, posterior interval, threshold probability, and decision rule together. If reasonable priors do lead to opposite decisions, make that dependence visible rather than choosing the most convenient prior.
What should a release reviewer conclude when one justified prior passes the deployment threshold but another equally defensible prior fails it?
Answer
The available sample doesn't support a prior-robust decision. Record both priors and posterior results, keep the release undecided, and collect additional representative tasks before claiming the threshold is met.