Capabilities
Quick start
QWED-Logic DSL
QWED uses a secure S-expression DSL for logic expressions.Operators
Type rules
Every variable has a sort:Int, Real, or Bool. An undeclared variable is an Int. Declare other sorts in the variables argument of DSLLogicVerifier.verify_from_dsl(), for example variables=[{"name": "flag", "type": "Bool"}]. On POST /verify/logic the query is natural language, and the translator declares the variables it emits. A (VAR flag Bool) form inside a larger expression does not compile, even though the compiler’s error message suggests it.
The compiler rejects mixed Boolean and numeric operands instead of coercing True to 1. A rejected program returns ERROR, never SAT or VERIFIED.
QWED does not infer the sort of an undeclared name from a Boolean literal, because that would hide a translation that forgot the declaration.
Examples
Satisfiability checking
Basic check
Finding all solutions
The SDK client’sverify_logic() returns a single verdict with one model. For all-solutions enumeration, iterate with a low-level solver (e.g. z3) or use the LogicVerifier low-level API and extend constraints yourself:
Business rules
These rules compare flags such ashas_id with True, so each flag must be declared Bool, or the compiler returns ERROR (see Type rules). The examples here and under Quantifiers run the DSL directly with DSLLogicVerifier, which takes the declarations in variables. verdict() returns SAT or UNSAT and fails closed on anything else:
Age verification
Discount eligibility
Approval workflow
Quantifiers
FORALL and EXISTS take exactly two arguments: a binder and a body.
- Binder. The first argument is a single plain variable name. The parser returns
BLOCKEDfor any other binder, such as a number, an operator name,True/False, a list like(x), or an expression. - Sort. The bound variable takes its declared sort. If it isn’t declared, it is an
Int. - Scope. The binding applies inside the body only. A free variable with the same name outside the quantifier is a separate variable.
- Body. The body must be Boolean, such as a comparison or a logical expression. A numeric body returns
ERROR. - Solver limits. Quantified formulas can be hard for Z3. If Z3 returns
unknown, for example on a timeout,verify_from_dsl()returnsUNKNOWNandPOST /verify/logicreturnsBLOCKED. Neither is verified.
(quantity item) returns BLOCKED with Unknown operator.
Universal (FORALL)
Existential (EXISTS)
Bound and free variables
Security: operator whitelist
The DSL uses a strict whitelist — only approved operators are allowed:Fail-closed constraint parsing
The logic engine usesSafeEvaluator for all constraint parsing. If SafeEvaluator is unavailable, the engine raises a RuntimeError instead of falling back to raw evaluation. This fail-closed design ensures that untrusted input is never passed to Python’s eval().
Provider tracking
When you verify a natural-language logic query, QWED translates it to DSL using the configured LLM provider. The response includes aprovider_used field so you can see which provider handled the translation — even when the request falls back to a different provider or ends in an error.
Python-style constraints
QWED also converts Python-style constraints from a structured logic task into DSL. It maps each operator to its whitelisted DSL name:
Constraints such as
x >= 5 or x * 2 <= 10 compile and return SAT or UNSAT. Earlier releases emitted GE, LE, NE, and MUL, so these constraints came back BLOCKED.
You can join conditions with infix and/or/not in any case, or with the call forms And(...) and Or(...):
Categorical string constants fail closed
The natural-language bridge rejects string literals such astier == 'gold'. The solver has no string type, so QWED can’t soundly model categorical string constants. A constraint that contains one returns UNVERIFIABLE (or an error) instead of a verdict. Floating-point literals are rejected the same way.
Encode categories as explicit integers instead:
Before v7.3.0, the bridge turned each string into a free integer variable, so contradictory categorical claims could come back satisfiable. See GHSA-g25v-44j8-gg7x.
Error handling
LogicVerifier returns DiagnosticResult (v6.0.0)
Introduced in v6.0.0.
LogicVerifier is the second engine — after FactVerifier — to conform to the unified DiagnosticResult contract. The legacy LogicResult dataclass has been removed from the verifier’s return surface. The /verify/logic endpoint, and so client.verify_logic(), maps the DSL verdict instead: SAT is VERIFIED with the model as evidence, UNSAT is UNVERIFIABLE, and anything else (UNKNOWN, ERROR, BLOCKED) is BLOCKED. DSLLogicVerifier.verify_from_dsl() returns the raw SAT, UNSAT, UNKNOWN, ERROR or BLOCKED.LogicVerifier class directly, every one of its nine public methods now returns a DiagnosticResult:
verify_logicverify_with_quantifiersverify_bitvectorverify_arrayprove_theoremcheck_implicationcheck_equivalenceverify_optimizationcheck_vacuity
Reading a result
agent_message, developer_fields, and proof_ref — are populated on every result. proof_ref is computed from the Z3 solver’s assertion stack and is present only when status == "VERIFIED".
SAT/UNSAT status matrix
Z3’ssat/unsat outcome has different meanings depending on the method. The engine disambiguates by mapping each per-method outcome to a DiagnosticResult status and recording the raw Z3 verdict under developer_fields.deterministic_verdict.
symbol_table on every VERIFIED result
developer_fields.symbol_table is a sorted list of every declared variable and its type. It is included on every VERIFIED result so audit logs record exactly what was proven:
New BLOCKED constraint IDs
Two fail-closed behaviors ship with the migration:
Other
constraint_ids used by the engine: logic_verifier.invalid_constraint, logic_verifier.unknown_quantifier, and logic_verifier.execution_error.
Migrating from LogicResult
Status field
BitVec declarations fail closed
Performance
Next steps
- DSL reference - Complete operator documentation
- Verification diagnostics - The 3-layer
DiagnosticResultmodel - Code engine - Verify code correctness
- SQL engine - Verify SQL queries