For agents reading this codebase: this document explains why the
elicit_*crates exist, what problem they solve, and how they fit into the broader goal of compositional formal verification.
Formal verification tools (Creusot, Kani, Verus) are powerful, but they face a practical barrier: the unit of proof is the method, and methods are complex. A non-trivial method may span dozens of lines, call other methods, mutate state across branches, and carry implicit invariants that are hard to express as pre/postconditions. Writing and maintaining those proofs by hand is expensive.
The standard response is to verify small utility functions and trust the rest. This leaves the most important code — complex business logic — unverified.
This project inverts the relationship between methods and verification.
Instead of writing a method and then verifying it, we:
- Define a vocabulary of atomic, verified operations — the building blocks of a crate (type constructors, methods, trait impls), each with a simple, machine-checkable contract
- Let an agent compose those operations into a tool chain
- The tool chain is the method — its correctness follows structurally from the correctness of each link plus the type-safety of the connections
The agent is not generating arbitrary Rust code. It is doing proof search: finding a sequence of verified steps that transforms the input types into the desired output types while satisfying all intermediate contracts.
Atomic operations (verified individually)
↓ composed by agent under type-theoretic constraints
Tool chain (correct by construction)
↓ emitted as Rust source
Formally verified method ∎
A shadow crate (elicit_*) is a crate-shaped vocabulary for a third-party
library. It exposes three things:
| Layer | What it provides | Example |
|---|---|---|
| Types | serde + JsonSchema wrappers so values can cross the MCP boundary |
elicit_clap::Command wraps clap::Command |
| Methods | #[reflect_methods] exposes instance methods as MCP tools |
Arg::get_long(), Command::get_name() |
| Traits | #[reflect_trait] factories expose derive-trait methods per registered type |
clap::ValueEnum::value_variants(), serde::Serialize |
Together these form a complete vocabulary for the library. An agent that has access to all three layers can reason about and compose the library's behaviour without writing a single line of Rust.
Upstream crates intentionally omit serde/schemars impls (they live at the
CLI or data boundary, not the serialization boundary). The orphan rule prevents
adding those impls in a downstream crate. Newtypes sidestep both constraints:
the wrapper is ours, so we can impl any trait on it, and From/Into provide
the lossless bridge back to the original type.
An agent given a goal expressed as types reasons as follows:
- What type do I need to produce? (the postcondition)
- What types do I have? (the current state)
- Which tool call advances me from current state toward the goal? (proof step)
- Does the resulting type satisfy the next tool's precondition? (type check)
- Repeat until the goal type is in hand.
This mirrors how a human programmer writes a method body — tracing cause and effect through a sequence of function calls — but every step is an explicit, auditable tool invocation with a known contract.
The agent is not writing a method; it is discovering one through typed composition. The method body emerges from the tool chain.
Per-tool contracts — each atomic operation carries a Creusot/Kani/Verus
proof of its own behaviour (see FORMAL_VERIFICATION_LEGOS.md and
THIRD_PARTY_SUPPORT_GUIDE.md).
Chain-level verification — the type system enforces that the output type of each step matches the input type of the next. An ill-typed composition is rejected at registration time, not at runtime. For stronger guarantees, Creusot logic annotations on the wrapper types carry invariants across the chain.
The result is a proof structure that scales: verifying 100 atomic operations once is far cheaper than verifying every combination of those operations in every method that could be written from them.
The long-term goal is that complex behaviour in this ecosystem is not hand- written Rust. Instead:
- A human defines the types (the vocabulary of the domain)
- An agent composes tool chains that operate on those types
- Formal verification guarantees the chains are correct
- The chains are emitted as Rust source and compiled
This is how we get to verified methods without paying the cost of per-method proof maintenance. The proof work is front-loaded into the atomic building blocks; the composition is safe by construction.
Shadow crates are the dictionary. Tool chains are the sentences. The type system is the grammar that makes every grammatical sentence meaningful.
| Document | What it covers |
|---|---|
README.md |
Crate overview and quick start |
THIRD_PARTY_SUPPORT_GUIDE.md |
How to add a new shadow crate |
FORMAL_VERIFICATION_LEGOS.md |
Compositional proof strategy |
ELICITATION_WORKFLOW_ARCHITECTURE.md |
Workflow infrastructure deep dive |
CREUSOT_GUIDE.md |
Creusot-specific annotation patterns |
crates/elicit_clap/ |
Canonical reference implementation |