Probabilistic Verification¶
Source: examples/probabilistic_verification.py
This example uses Discrete-Time Markov Chains (DTMC) to model and verify probabilistic properties of stochastic agent systems -- LLM retry logic, provider fallback chains, and consensus convergence.
1. LLM Agent Retry Model¶
An LLM agent that calls an API, handling transient and permanent errors.
from agenticraft_foundation.verification import (
DTMC, check_reachability, expected_steps, steady_state,
)
dtmc = DTMC()
dtmc.add_state(0, labels={"idle"})
dtmc.add_state(1, labels={"calling_llm"})
dtmc.add_state(2, labels={"success"})
dtmc.add_state(3, labels={"transient_error"})
dtmc.add_state(4, labels={"permanent_error"})
dtmc.add_transition(0, 1, probability=1.0)
dtmc.add_transition(1, 2, probability=0.85) # 85% success
dtmc.add_transition(1, 3, probability=0.12) # 12% transient
dtmc.add_transition(1, 4, probability=0.03) # 3% permanent
dtmc.add_transition(3, 1, probability=1.0) # retry
dtmc.add_transition(2, 2, probability=1.0) # absorbing
dtmc.add_transition(4, 4, probability=1.0) # absorbing
dtmc.validate()
Transition probabilities from each state must sum to 1.0. The validate() method checks this.
Reachability Probability¶
"What's the probability of eventually reaching success?"
result = check_reachability(dtmc, target_labels={"success"})
print(f"P(success) = {result.probability:.6f}") # 0.965909
The system solves a linear system: \(p(s) = \sum_{s'} P(s, s') \cdot p(s')\) with boundary conditions \(p(\text{success}) = 1\) and \(p(\text{permanent\_error}) = 0\).
The retry loop amplifies the per-attempt 85% rate to 96.6% overall -- but the 3% permanent error rate caps it.
Expected Steps¶
steps = expected_steps(dtmc, target_labels={"success"})
print(f"E[steps] = {steps.expected:.4f}") # 2.2727
On average, 2.27 steps to reach success (accounting for retries).
2. Provider Fallback Chain¶
Three providers with decreasing reliability as fallbacks.
fallback = DTMC()
fallback.add_state(0, labels={"start"})
fallback.add_state(1, labels={"primary"}) # 95% success
fallback.add_state(2, labels={"secondary"}) # 90% success
fallback.add_state(3, labels={"tertiary"}) # 80% success
fallback.add_state(4, labels={"success"})
fallback.add_state(5, labels={"total_failure"})
fallback.add_transition(0, 1, probability=1.0)
fallback.add_transition(1, 4, probability=0.95)
fallback.add_transition(1, 2, probability=0.05)
fallback.add_transition(2, 4, probability=0.90)
fallback.add_transition(2, 3, probability=0.10)
fallback.add_transition(3, 4, probability=0.80)
fallback.add_transition(3, 5, probability=0.20)
fallback.add_transition(4, 4, probability=1.0)
fallback.add_transition(5, 5, probability=1.0)
result = check_reachability(fallback, target_labels={"success"})
print(f"P(success) = {result.probability:.6f}") # 0.999000
The cascade: \(0.95 + 0.05 \times 0.90 + 0.05 \times 0.10 \times 0.80 = 0.999\). Three nines of reliability from providers that individually offer far less.
3. Consensus Convergence¶
A gossip-based consensus model where convergence probability increases per round.
consensus = DTMC()
consensus.add_state(0, labels={"round_0", "diverged"})
consensus.add_state(1, labels={"round_1", "diverged"})
consensus.add_state(2, labels={"round_2", "diverged"})
consensus.add_state(3, labels={"converged"})
consensus.add_transition(0, 1, probability=0.6)
consensus.add_transition(0, 3, probability=0.4)
consensus.add_transition(1, 2, probability=0.3)
consensus.add_transition(1, 3, probability=0.7)
consensus.add_transition(2, 3, probability=1.0)
consensus.add_transition(3, 3, probability=1.0)
steps = expected_steps(consensus, target_labels={"converged"})
print(f"E[rounds] = {steps.expected:.4f}") # 1.7800
Guaranteed convergence within 3 rounds, expected in under 2.
4. Ergodic Steady-State¶
An agent cycling through think/act/observe states indefinitely.
cycle = DTMC()
cycle.add_state(0, labels={"thinking"})
cycle.add_state(1, labels={"acting"})
cycle.add_state(2, labels={"observing"})
cycle.add_transition(0, 1, probability=0.8)
cycle.add_transition(0, 2, probability=0.2)
cycle.add_transition(1, 2, probability=1.0)
cycle.add_transition(2, 0, probability=1.0)
dist = steady_state(cycle)
The steady-state distribution \(\pi\) satisfies \(\pi = \pi P\):
| State | Long-run fraction |
|---|---|
| thinking | 35.7% |
| acting | 28.6% |
| observing | 35.7% |
This tells you the long-run resource allocation: the agent spends about a third of its time thinking, a third observing, and slightly less acting.
Output¶
=== LLM Agent Retry Model ===
P(eventually success) = 0.965909
E[steps to success] = 2.2727
=== Provider Fallback Chain ===
P(success with fallback chain) = 0.999000
P(total failure) = 0.001000
=== Consensus Convergence ===
P(eventually converge) = 1.0000
E[rounds to converge] = 1.7800
=== Ergodic Agent Cycle ===
thinking: 35.7%
acting: 28.6%
observing: 35.7%