Files
proofs/lean/Agent/Gym.lean
zooqueenandHanzo Dev d3bff5e2dc proofs: drive Lean library to 0 errors, eliminate proof-by-assumption axioms
The lean/ proof library (88 files) had ~180 type-check errors across ~45 files
plus 31 proof-by-assumption axiom cheats. It now type-checks cleanly under
Lean 4.14.0 + Mathlib, with every theorem genuinely proven from definitions.

Errors fixed:
- Stale Mathlib imports: Data.Nat.Basic / Data.Int.Basic were removed this rev
  -> Data.Nat.Defs / Data.Int.Defs (57 files).
- Shared AI/Agent/Governance/Contract/DeFi/Ecology draft files: same proof-rot
  as the zoo-labs sibling library (now reconciled): omega missing in-scope
  bounds, dead tactics after simp, simp over-reduction, doc-comment placement,
  reserved-word binders, deriving Repr on Finset-bearing structs, noncomputable
  defs over opaque primitives.
- Consensus/Liveness: collapsed a 74-error cascade (undefined shared state model
  inlined from Safety.lean; the |>. pipe-projection vs comparison parse trap on
  14 lines rewritten to explicit dot projection).
- Crypto (BLS/FROST/Corona/Hybrid/threshold/FHE), Consensus, Protocol, Warp,
  Trust, GPU, Compute, Network, DeFi: brought structure invariants into scope
  for omega, Nat-division bounds via Nat.div_le_of_le_mul / Nat.div_mul_le_self,
  noncomputable over opaque crypto ops, deriving Inhabited for index access.

Integrity:
- Removed all 31 `theorem X := X_ax` cheats (axiom asserting the identical
  statement) and proved each from definitions; removed the UNSOUND
  content_addressed axiom (hash-injectivity over a struct with free fields).
- Quorum-intersection theorems (BFT/Quasar/Flare/Warp/BLS/GPU/Bridge): the draft
  hypothesis 3f<n is insufficient (n unbounded above makes them false); tightened
  to the optimal-resilience invariant n=3f+1 / 2t>n. Statement intent preserved.
- Net axiom change: -35 / +1 (the one addition, honesty_static, is a
  fixed-validator-set modeling assumption matching sibling files).
- Remaining axioms are trusted-base only: opaque crypto primitives (groups,
  pairings, hardness, hashes, FHE encrypt/decrypt, BLS soundness, forward
  secrecy, Fisher-Yates uniformity) and protocol modeling assumptions. No
  sorry/admit/native_decide. Verified all theorems trace to
  [propext, Classical.choice, Quot.sound] plus those primitives.

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-02 05:54:28 -07:00

198 lines
8.2 KiB
Lean4

/-
Authors: Antje Worring, Zach Kelling
Agent Training Environment (Gym) Safety Properties
Formal proofs about the Gym training platform's environment safety.
Gym is an open-source AI model training platform supporting 100+
model architectures with training-free GRPO integration.
Maps to:
- zoo/papers/gym-training-platform.tex: Gym specification
- zoo/papers/hllm-training-free-grpo.tex: HLLM/TF-GRPO foundation
- github.com/zooai/gym: implementation
Key results:
- Environment state stays within defined bounds
- Reward function is consistent across episodes
- Exploration strategy covers all reachable states
Author: Zach Kelling
-/
import Mathlib.Data.Nat.Defs
import Mathlib.Data.List.Basic
import Mathlib.Data.Finset.Basic
import Mathlib.Tactic
namespace Agent.Gym
/-! ## Core Structures -/
/-- Environment state is a bounded integer vector (abstracted as Nat with bounds). -/
structure EnvState where
/-- State value (abstracted to single dimension for formal treatment) -/
value : Nat
/-- Lower bound on state -/
lo : Nat
/-- Upper bound on state -/
hi : Nat
/-- Invariant: state is within bounds -/
hBounded : lo ≤ value ∧ value ≤ hi
deriving DecidableEq
/-- An action modifies the state within bounds. -/
structure Action where
/-- Delta applied to state (can be negative, represented as Int) -/
delta : Int
/-- An episode is a sequence of (state, action, reward) tuples. -/
structure Step where
state : EnvState
action : Action
reward : Nat
abbrev Episode := List Step
/-- A reward function maps (state, action) pairs to rewards. -/
abbrev RewardFn := EnvState → Action → Nat
/-- The set of reachable states from a given state. -/
abbrev ReachableSet := Finset Nat
/-! ## Theorem 1: Environment State Bounded -/
/-- Clamping an Int between lo and hi then converting to Nat preserves bounds,
provided the window is well-formed (`lo ≤ hi`, which every `EnvState`
guarantees via `hBounded`). The lower bound holds because `max lo _ ≥ lo ≥ 0`
survives `Int.toNat`; the upper bound because `min raw hi ≤ hi` and `lo ≤ hi`
give `max lo (min raw hi) ≤ hi`. -/
theorem clamp_bounded (lo hi : Nat) (raw : Int) (hle : lo ≤ hi) :
lo ≤ (max (lo : Int) (min raw (hi : Int))).toNat ∧
(max (lo : Int) (min raw (hi : Int))).toNat ≤ hi := by
constructor
· have h1 : (lo : Int) ≤ max (lo : Int) (min raw (hi : Int)) := le_max_left _ _
have h0 : (0 : Int) ≤ (lo : Int) := Int.ofNat_nonneg lo
rw [Int.le_toNat (le_trans h0 h1)]
exact h1
· have hub : max (lo : Int) (min raw (hi : Int)) ≤ (hi : Int) := by
apply max_le
· exact_mod_cast hle
· exact min_le_right _ _
have : (max (lo : Int) (min raw (hi : Int))).toNat ≤ ((hi : Int)).toNat :=
Int.toNat_le_toNat hub
simpa using this
/-- Apply an action to a state, clamping to bounds. -/
def applyAction (s : EnvState) (a : Action) : EnvState :=
let raw := (s.value : Int) + a.delta
let clamped := max (s.lo : Int) (min raw (s.hi : Int))
{ value := clamped.toNat
lo := s.lo
hi := s.hi
hBounded := clamp_bounded s.lo s.hi ((s.value : Int) + a.delta)
(le_trans s.hBounded.1 s.hBounded.2) }
/-- Environment state remains bounded after any action.
The Gym platform enforces this via clamping: any action that would
push the state out of bounds is truncated to the boundary.
This prevents undefined behavior in the training loop. -/
theorem env_state_bounded (s : EnvState) (a : Action) :
let s' := applyAction s a
s'.lo ≤ s'.value ∧ s'.value ≤ s'.hi := by
exact (applyAction s a).hBounded
/-- Bounds are preserved: the action doesn't change the bound definition. -/
theorem bounds_preserved (s : EnvState) (a : Action) :
(applyAction s a).lo = s.lo ∧ (applyAction s a).hi = s.hi := by
simp [applyAction]
/-- After a full episode, all intermediate states are bounded. -/
theorem episode_states_bounded (ep : Episode)
(h : ∀ step ∈ ep, step.state.lo ≤ step.state.value ∧ step.state.value ≤ step.state.hi) :
∀ step ∈ ep, step.state.lo ≤ step.state.value := by
intro step hmem
exact (h step hmem).1
/-! ## Theorem 2: Reward Consistency -/
/-- A reward function is consistent if it is deterministic: the same
(state, action) pair always produces the same reward.
This is a fundamental requirement for GRPO's group-relative advantage
computation to be meaningful across episodes. -/
def rewardConsistent (rf : RewardFn) : Prop :=
∀ s₁ s₂ : EnvState, ∀ a : Action,
s₁.value = s₂.value → s₁.lo = s₂.lo → s₁.hi = s₂.hi →
rf s₁ a = rf s₂ a
/-- If the reward function is deterministic in state (value, lo, hi) and action,
then two trajectories that agree pointwise on those state components and on the
chosen actions induce the same sequence of rewards *as computed by `rf`*.
Note: we phrase the conclusion over `rf` applied at each step
(`fun st => rf st.state st.action`), since that is the reward the deterministic
function assigns. The hypotheses give full state agreement (value, lo, hi) so that
`rewardConsistent` applies. Proof: `List.ext_getElem` reduces to pointwise equality,
then `rewardConsistent` closes each index. -/
theorem reward_consistent (rf : RewardFn) (ep₁ ep₂ : Episode)
(hConsistent : rewardConsistent rf)
(hSameLen : ep₁.length = ep₂.length)
(hSameStates : ∀ i : Fin (min ep₁.length ep₂.length),
(ep₁.get ⟨i.val, by omega⟩).state.value = (ep₂.get ⟨i.val, by omega⟩).state.value ∧
(ep₁.get ⟨i.val, by omega⟩).state.lo = (ep₂.get ⟨i.val, by omega⟩).state.lo ∧
(ep₁.get ⟨i.val, by omega⟩).state.hi = (ep₂.get ⟨i.val, by omega⟩).state.hi)
(hSameActions : ∀ i : Fin (min ep₁.length ep₂.length),
(ep₁.get ⟨i.val, by omega⟩).action = (ep₂.get ⟨i.val, by omega⟩).action) :
ep₁.map (fun st => rf st.state st.action) = ep₂.map (fun st => rf st.state st.action) := by
apply List.ext_getElem
· simp [hSameLen]
· intro i h1 h2
simp only [List.getElem_map]
have hi_lt : i < min ep₁.length ep₂.length := by
simp only [List.length_map] at h1 h2; omega
have hs := hSameStates ⟨i, hi_lt⟩
have ha := hSameActions ⟨i, hi_lt⟩
have e1 : ep₁[i] = ep₁.get ⟨i, by simp only [List.length_map] at h1; omega⟩ := by rfl
have e2 : ep₂[i] = ep₂.get ⟨i, by simp only [List.length_map] at h2; omega⟩ := by rfl
rw [e1, e2, ha]
exact hConsistent _ _ _ hs.1 hs.2.1 hs.2.2
/-- A bounded reward function produces rewards in [0, rmax]. -/
def rewardBounded (rf : RewardFn) (rmax : Nat) : Prop :=
∀ s : EnvState, ∀ a : Action, rf s a ≤ rmax
/-! ## Theorem 3: Exploration Coverage -/
/-- An exploration strategy is a function from state to action distribution.
Here we model it as producing a list of actions to try. -/
abbrev ExplorationStrategy := EnvState → List Action
/-- The set of states reachable from a starting state via a strategy.
Computed by applying all actions and collecting resulting state values. -/
def reachableFrom (s : EnvState) (strategy : ExplorationStrategy) : List Nat :=
(strategy s).map (fun a => (applyAction s a).value)
/-- Coverage property: given enough episodes, the exploration strategy
visits every reachable state at least once.
This is critical for GRPO's group-relative advantage computation:
if some states are never visited, the advantage estimates are biased.
We state this as: for any reachable state value v, there exists an
action in the strategy that reaches v from some starting state. -/
theorem exploration_covers (s : EnvState) (strategy : ExplorationStrategy)
(v : Nat) (hReachable : v ∈ reachableFrom s strategy) :
∃ a ∈ strategy s, (applyAction s a).value = v := by
simp [reachableFrom] at hReachable
obtain ⟨a, ha_mem, ha_val⟩ := hReachable
exact ⟨a, ha_mem, ha_val⟩
/-- If the strategy produces at least one action for every state,
the reachable set is nonempty. -/
theorem exploration_nonempty (s : EnvState) (strategy : ExplorationStrategy)
(h : (strategy s).length > 0) :
(reachableFrom s strategy).length > 0 := by
simp only [reachableFrom, List.length_map]
exact h
end Agent.Gym