Elicitation
Structured agent interaction backed by formal verification.
What is Elicitation?
An MCP agent calling your tools can pass anything, in any order. Elicitation inverts that:
instead of the agent pushing arbitrary values into your functions, your types pull values from
the agent through a verified round-trip. The agent sees a prompt; you get a validated, type-safe
result. That is what elicit() does.
The rest of the framework builds on that foundation:
- Shadow crates give agents a verified vocabulary for third-party libraries
- Contracts make invalid workflow sequences fail at compile time, not runtime
- Verified State Machines combine all three into formally proven programs
Every participating type carries proofs for three independent verifiers (Kani, Verus, Creusot),
composed automatically from its fields by #[derive(Elicit)].
Deriving Agent-Native Types
#[derive(Elicit)] generates elicit(), the MCP JSON schema, and proof methods by composing
the impls of its fields. Add a field, get its proof for free:
DeployConfig::elicit(&server).await? drives a back-and-forth with the agent, prompting
each field in turn and validating responses. The agent cannot skip fields or bypass constraints.
Interaction paradigms
The derive picks the right paradigm from the shape of the type:
| Trait | Paradigm | Used for |
|---|---|---|
Select |
Choose from a finite set | Enums |
Affirm |
Binary yes/no | bool fields, guard steps |
Survey |
Sequence of field elicitations | Structs |
Style System
A terse machine prompt is noise to a human; a friendly wizard prompt wastes an agent's context. Name your audience on the field — the derive generates the style enum; swap it at the call site:
pub environment: Environment,
let config = elicit.await?;
Generators
Generator<Target> produces values of a target type from any source — sensor fusion,
lookup tables, physics steps. elicitation_rand adds #[rand(...)] field attributes
(bounded(L, H), positive, nonzero, even, odd) and derives a seeded
random_generator(seed: u64) for deterministic property testing, with its own Kani suite.
Shadow Crates — Familiar APIs Over MCP
A shadow crate (elicit_*) mirrors the API of its source library with MCP-compatible types and
tools that use the same names and types developers already know. The goal is that an agent working
with elicit_reqwest feels like it is working with reqwest — the vocabulary is familiar, the
types correspond, but everything crosses the MCP boundary safely.
Each shadow crate exposes three layers:
| Layer | What it provides | Mechanism |
|---|---|---|
| Types | MCP-safe wrappers with the source library's names | Newtypes |
| Methods | Instance methods as MCP tools | #[reflect_methods] |
| Traits | Third-party trait methods as typed factories | #[reflect_trait] |
;
// Generates: arg__get_long, arg__get_short, arg__get_help, ... as MCP tools
For compile-time macros (e.g. sqlx::query!), fragment tools emit verified Rust source via
EmitCode; the macro expands in the consumer binary where a live connection is available.
| Domain | Shadow crates |
|---|---|
| Web / HTTP | elicit_reqwest, elicit_axum, elicit_tower |
| Database | elicit_sqlx, elicit_redb, elicit_db |
| Async runtime | elicit_tokio |
| CLI | elicit_clap |
| UI / graphics | elicit_egui, elicit_ratatui, elicit_bevy, elicit_wgpu, elicit_winit, elicit_accesskit, elicit_leptos, elicit_ui |
| GIS / geometry | elicit_geo, elicit_geo_types, elicit_geojson, elicit_georaster, elicit_gis, elicit_proj, elicit_rstar, elicit_wkb, elicit_wkt |
| Date / time | elicit_chrono, elicit_jiff, elicit_time |
| Data formats | elicit_csv, elicit_serde, elicit_serde_json, elicit_toml |
| String / type utilities | elicit_url, elicit_regex, elicit_uuid, elicit_uom, elicit_std |
Contracts — Invariants as Types
Programs have invariants that hold regardless of execution path. Most frameworks leave them as comments or runtime checks — things developers know but the compiler doesn't. Elicitation makes them explicit in the type system.
A Prop is a marker type: a name for an invariant. Established<P> is a zero-sized proof
token that P holds — it exists only because something produced it, and the only way to produce
it is through a ProvableFrom exchange:
;
;
// ProvableFrom is a sealed exchange: present ConnectionReady, receive QueryValidated
The exchange lives in a trait method — and that trait method is the only door to the token:
Because the trait method is the only constructor for Established<QueryValidated>, the business
logic must run. The compiler enforces it. Proofs can then verify that this chain is sound.
And<P,Q> composes proofs; both(p, q) constructs conjunctions. Shadow crates ship their own
Prop types: elicit_sqlx has DbConnected, QueryExecuted, TransactionOpen;
elicit_reqwest has UrlValid, RequestCompleted, FetchSucceeded.
Verified State Machines
A Verified State Machine is a state machine where every state implements Elicitation and
every transition is a #[formal_method]. That dual constraint is exactly what the derive needs
to auto-generate proofs: all inputs and outputs are within the verified type system, and all
transitions follow the ProvableFrom contract — so there is no path through the machine that
the framework cannot inspect and verify.
// All states implement Elicitation
// Consistency predicate — the invariant every transition must preserve
// All transitions are #[formal_method] — the only doors between states
#[formal_method] gates #[instrument] so proof toolchains don't chase tracing internals,
and registers the transition for harness generation. The generated proofs verify that every
reachable transition preserves the consistency predicate — correctness by construction.
Formal Verification
The proof infrastructure is bottom-up. The core elicitation crate ships verified proofs for
stdlib and third-party types behind feature gates — capturing meaningful bounds: a PortNumber
is always in [1024, 65535]; a NonEmptyString always has length > 0; an
Established<FetchSucceeded> is always produced from a 2xx response. When you derive Elicit
on your own types, your proofs compose from those canonical proofs. A struct's proof is the
union of its fields' proofs — add a field, get its verification bounds for free.
At the VSM level, proofs verify the richer claim: that every transition preserves the consistency predicate. Three verifiers approach this from independent directions:
| Verifier | Approach | Current workspace coverage |
|---|---|---|
| Kani | Bounded model checking — exhaustive within bound | 388 passing harnesses |
| Verus | SMT-based program logic | 158 passing proofs |
| Creusot | Deductive verification — rich compositional invariants | 22,837 valid goals / 19 modules |
Visibility
An agent working with an unfamiliar codebase can't read source. Three features expose the vocabulary at runtime without flooding the context window:
TypeSpec surfaces ElicitSpec annotations as MCP tools. describe_type returns a
summary and list of available categories; explore_type returns one category — requires,
ensures, bounds, fields — on demand. Agents pull only what they need.
TypeGraph renders structural hierarchies of registered types as Mermaid or DOT graphs.
list_types() → graph_type("ApplicationConfig") shows how NetworkConfig, Role, and
DeploymentMode compose into it, in one tool call.
ElicitIntrospect exposes zero-allocation pattern() and metadata() for labelling
tracing spans and Prometheus metrics — no source parsing, no allocation:
ELICITATION_COUNTER
.with_label_values
.inc;
#[derive(Elicit)] registers types for all three via inventory::submit! automatically.
Getting Started
[]
= "0.11"
= "1"
= "0.8"
= { = "1", = ["derive"] }
= { = "1", = ["full"] }
Derive Elicit on your domain types, call elicit() inside a tool handler, and expose
via elicit_tools!:
Add shadow crate plugins alongside your own server via PluginRegistry:
new
.register_flat
.register
.register
.serve
.await?;
One registry, one transport, zero glue code.
Workspace Crate Map
| Crate | Role |
|---|---|
elicitation |
Core: traits, contracts, MCP plumbing |
elicitation_derive |
Proc macros: #[derive(Elicit)], #[formal_method], #[reflect_methods], #[reflect_trait] |
elicitation_rand |
Randomised value generation |
elicit_proofs |
Generated harnesses (Kani / Verus / Creusot) |
elicitation_kani / elicitation_creusot |
Proof gallery and support |
elicit_server |
MCP server support |
elicit_clap |
CLI vocabulary (canonical shadow crate reference) |
elicit_* |
30+ domain shadow crates — see Shadow Crates above |
Further Reading
| Document | Topic |
|---|---|
SHADOW_CRATE_MOTIVATION.md |
The inversion thesis |
THIRD_PARTY_SUPPORT_GUIDE.md |
Adding a new shadow crate |
ELICITATION_WORKFLOW_ARCHITECTURE.md |
Workflow infrastructure |
CREUSOT_GUIDE.md |
Creusot annotation patterns |
FORMAL_VERIFICATION_LEGOS.md |
Compositional proof strategy |
crates/elicit_clap/ |
Canonical reference shadow crate |