Palamedes
Palamedes is a Lean 4 library that synthesizes random generators from logical predicates. Given a
predicate P : α → Prop (or a decidable α → Bool), the generator_search tactic produces a Gen α whose support is exactly P — it can produce every value satisfying P and nothing else.
Synthesis runs at elaboration time, inside a proof, driven by
Aesop.
This is research code accompanying a PLDI 2026 paper. The core pipeline works, but there are rough edges and all APIs should be considered unstable. We will read issues that are posted, but make no promises about how quickly they will be addressed.
Requirements
The toolchain is pinned in lean-toolchain (Lean v4.30.0). Dependencies — MathLib, Aesop, and
Plausible — are pinned in lakefile.toml / lake-manifest.json.
Building
lake build # build the library and the full example corpus
lake build Palamedes # build just the library
lake build PalamedesTest.Examples.Simple.Eq2 # build/elaborate a single example (module path)
lake build InProgress # build the known-failing WIP corpus (expected to fail)
There is no separate test framework. Every file under PalamedesTest/Examples/ synthesizes a
generator at elaboration time and fails to compile if synthesis fails, so a plain lake build (what
CI runs) also runs the tests. PalamedesTest/ExtractionAudit.lean additionally guards against
silent extraction failures by checking that no synthesis residue survives in the compiled
generators.
Usage
Import Palamedes.Synthesizer and call generator_search with a predicate:
import Palamedes.Synthesizer
open Gen CorrectGen
def genEq2 : Gen Nat := by
generator_search (· = 2)
The predicate may also be a recursive decidable property over a supported datatype:
@[simp]
def isAllTwos : List Nat → Bool
| [] => true
| x :: xs => x = 2 && isAllTwos xs
def genAllTwos : Gen (List Nat) := by
generator_search (fun xs => isAllTwos xs)
Useful variants:
generator_search? Pemits the synthesized term as a suggestion (so it can be pasted in place of the tactic call).generator_search P allow_partialskips the totality check, which allows generators that may need to backtrack at sample time.
To draw values, import Palamedes.Sample (or the Palamedes root, which re-exports both the tactic
and the sampler) and interpret a Gen as a runnable sampler on top of Plausible:
import Palamedes
-- draw 10 values from genAllTwos
#eval Gen.sampleN 10 genAllTwos
Gen.sample draws a single value, and SampleConfig controls size and backtracking behavior.
Layout
Palamedes/Gen.lean— theGentype (an inductive:ret,bind,pick,indexed,assume) andsupport : Gen α → α → Prop, the set of values a generator can produce, plussimplemmas describing howsupportdistributes over each constructor.Support.leanadds furthersupportrewriting lemmas.Palamedes/CorrectGen.lean—CorrectGen P := {g : Gen α // g.support = P}, a generator bundled with a proof that its support equals the target predicate, along with the basic synthesis rules that combineCorrectGens.Palamedes/Synthesizer/—CGeneratorSearch.leanregisters the synthesis rules with Aesop and defines thecgenerator_searchtactic;FrontEnd.leandefines the user-facinggenerator_searchtactic (search, extract a rawGen, optimize, re-prove support, and prove totality);Totality.leanproves generators never need to backtrack.Palamedes/Extract.leanandOptimizer.lean— extracting a plainGenout of aCorrectGenterm and simplifying it (monad laws, bind reassociation), with theoptimalitytactic re-proving that support is preserved.Palamedes/Sample.lean— interprets aGenas a runnable sampler with backtracking and size configuration (SampleConfig).Palamedes/Data/— per-datatype machinery the synthesizer needs (base functors, recursion schemes, synthesis and case-analysis rules, support/termination lemmas). Covered types:Nat,Bool,Unit,Color,Tuple,List,Tree,Stack/, andSTLC/. Adding support for a new recursive datatype means adding aData/module of this shape and importing it inCGeneratorSearch.lean.Palamedes/RuleSets.leanandUtil.lean— the central Aesop rule-set declarations and shared meta-level helpers.PalamedesTest/Examples/— the example corpus.Simple/andRange/are the easiest starting points; each datatype directory also has examples using the harder, more general "unfold strategy" from the paper.InProgress/holds cases that do not yet synthesize.