Capabilities
Quick start
Core operations
1. Expression evaluation
Verify that an expression equals a value:2. Identity verification
Check if two expressions are mathematically equivalent:VERIFIED. When symbolic simplification is inconclusive, the engine samples five test points as a fallback. Only points that the engine can successfully evaluate count toward agreement — the engine skips domain-restricted expressions (e.g., log(x) at x = -1) rather than producing a false negative.
3. Derivatives
Verify calculus derivatives:4. Integrals
Verify indefinite and definite integrals:5. Limits
Financial calculations
Compound interest
Net present value (NPV)
Internal rate of return (IRR)
Cash flows with more than one sign change, or that fail to converge, are now rejected with
BLOCKED rather than returning a best-effort iterate. See verify_irr — convergence proof for the full state table.Fail-closed semantics
Three verification methods require an additional proof step before returningVERIFIED. When the underlying claim is ambiguous, incomplete, or numerically unproven, the engine returns BLOCKED or CORRECTION_NEEDED with structured diagnostics rather than a best-effort answer.
verify_statistics(statistic="mode") — unique mode required
mode verification returns VERIFIED only when a single value has the maximum frequency. When two or more values tie for the maximum frequency, the engine returns BLOCKED with an ambiguous_modes list — it will not heuristically pick one.
Response fields on the
BLOCKED case:
compute_statistics method returns an equivalent multimodal error on its own surface — see Stats engine — Error handling.
verify_matrix_operation(operation="eigenvalues") — cardinality match required
Eigenvalue verification requires the claimed list to have the same length as the calculated eigenvalue set, counting algebraic multiplicity. Previously the value comparison used zip, which silently truncated to the shorter list — so a claim of [2] for a matrix with eigenvalues [2, 3] could pass. The cardinality check now runs before the value comparison.
Response fields on the cardinality-mismatch case:
verify_irr — convergence proof
IRR verification now requires proof that Newton-Raphson converged before returning VERIFIED. Successful results include converged: true and iterations_used for auditability. The engine blocks inputs whose IRR is mathematically ambiguous or numerically unreachable rather than returning the current iterate.
Response fields:
Trust boundary
When you verify a natural language math query through the/verify/natural_language endpoint, the response includes a trust_boundary object. This object describes exactly what the pipeline proved and what it did not.
The overall status for natural language math queries is
INCONCLUSIVE because, while QWED evaluates the expression deterministically, it cannot verify that the LLM correctly interpreted the user’s intent. The trust_boundary gives you the information to decide whether the result is sufficient for your use case.Error handling
When verification fails, QWED provides detailed error information:Exact SymPy arithmetic
When SymPy is available, the math engine evaluates expressions using SymPy-native types (sympy.Integer, sympy.Float) instead of Python built-in int and float. This prevents floating-point drift during intermediate computation and ensures that comparisons between LLM answers and verified results use symbolic simplification rather than string matching alone.
Decimal precision
The math engine acceptsDecimal values for exact arithmetic, which is especially useful for financial calculations:
use_decimal=True (the default), the engine uses Decimal internally regardless of whether you pass a float or Decimal.
Float precision advisory
When an expression submitted toPOST /verify/math contains binary floating-point constants (0.1, 1e9, 1.0j), the response carries a precision advisory in developer_fields.advisory_checks. Binary floats can be inexact relative to decimal arithmetic, so the advisory lists the offending constants and suggests decimal.Decimal or exact SymPy rationals (sympy.Rational) where exactness matters.
The advisory is informational only. It is an AdvisoryCheck, which enforces advisory_only=True at construction, so it structurally cannot change the verification status or proof_ref. Expressions with floats still verify normally.
The engine checks the expression exactly as you submitted it, before symbolic simplification. Implicit multiplication (
0.5x) is normalized for parsing but does not hide the float literal.
Tolerance settings
For floating-point comparisons, you can specify a tolerance:Tolerance bounding
To prevent inflated tolerances from masking incorrect results, the math engine enforces a deterministic upper bound on thetolerance parameter. QWED computes the maximum allowed tolerance as a function of the result’s magnitude:
BLOCKED status instead of returning a potentially misleading VERIFIED result. This applies to both decimal and float precision modes.
NaN, Infinity, or non-numeric strings) with a BLOCKED status and an "Invalid tolerance" error message.
Trust boundary
When math verification runs through the natural language pipeline (POST /verify/natural_language), the response includes a trust_boundary object. This object describes exactly what the pipeline proved and what it did not, separating deterministic expression evaluation from the non-deterministic LLM translation step — and records the mandatory attestation admission decision.
Because the LLM translation step is non-deterministic, the natural language pipeline returns
INCONCLUSIVE instead of VERIFIED even when the underlying expression evaluation succeeds. This prevents over-representing a translated-query evaluation as a proven user-query verdict. Use the direct POST /verify/math endpoint if you need a fully deterministic result without the LLM translation layer.Mandatory attestation admission
The control plane runs every math response throughenforce_trust_decision(..., require_attestation=True). Attestation is now an admission gate — a VERIFIED result is only returned once the signing step succeeds:
- On success: the verifier evidence (translated expression, calculated value, claimed value, diff, precision mode) is passed to
create_verification_attestation(). If the returnedAttestationResulthasstatus == ISSUED,enforce_trust_decisionaccepts the signed token and setsoverall_status = "VERIFIED". - On signing failure: the response is downgraded to
BLOCKEDandtrust_boundary.attestation_errorrecords the error code from theAttestationResult(for exampleSIGNING_FAILUREorCRYPTO_UNAVAILABLE). No token is returned. This is a fail-closed guarantee: signing outages never surface asVERIFIED. overall_statusis post-attestation. It is always sourced from the enforced decision, never from the raw verifier verdict. If you need to reason about the raw verifier output separately, inspectverification.is_correctanddeterministic_expression_evaluation.
Attestation scope: translated expression, not natural-language query
The attestationqwed.query_hash binds to the translated expression that the deterministic engine actually evaluated (task.expression), not to the user’s natural-language query. This matches the disclosed verification_scope: "translated_expression_only" and prevents the attestation from over-claiming that the user’s prose was verified.
response.user_query— the original natural-language query, kept for display and audit correlation.- Attestation
qwed.query_hash— SHA-256 of the translated deterministic expression (e.g.0.15 * 200), which is the exact input the engine executed. - Downstream verifiers should hash the translated expression when re-checking
qwed.query_hash. Hashinguser_querywill not match — that is intentional, because QWED does not attest to the LLM translation step.
Ambiguous expressions
Expressions with implicit multiplication after division are ambiguous — for example,1/2(3+1) could mean (1/2)*(3+1) or 1/(2*(3+1)). Rather than guessing, the math engine fails closed and returns BLOCKED:
* operator:
Expression input rules
Every math expression passes through a layered structural validator before SymPy evaluates it. The validator runs in order: NFKC Unicode normalization, an ASCII character-set gate, a construct denylist, and an AST node-type allowlist. An expression that fails any layer is rejected before evaluation. These are the rules your expressions must follow:
Additional details:
- Greek letters: the parser provides ASCII names for the full Greek alphabet (
alpha,beta,gamma, …omega). Writealpha * x, notα * x. Non-ASCII codepoints never reach the parser, including Unicode look-alikes that NFKC-normalize to ASCII identifiers. - Allowed characters: letters, digits, underscore, whitespace, and the operator set
+ - * / ( ) . , ^ %. - Allowed syntax: arithmetic operators, function calls from the safe namespace (
sin,cos,log,sqrt,factorial, …), symbol names, and numeric constants. Everything else is rejected by the AST allowlist. - Caret exponentiation:
^is converted to**by the default transformation pipeline, sox^2and2^3parse correctly. - Depth and length limits: expressions are capped at 5,000 characters, an AST depth of 30, and a SymPy tree depth of 40.
- Implicit multiplication (
2x,sin x,2(x+1)) still parses. These forms are covered by the character-set gate instead of the AST check.
These rules are structural security guarantees, not style preferences. Attribute access, string constants, and non-ASCII identifiers are the building blocks of sandbox-escape payloads, so the parser rejects them by construction rather than pattern-matching known attacks. Legitimate arithmetic never needs them.
Compute-cost bounds
SymPy expands integer powers eagerly and exactly. An expression like9^9^9^9 demands a result with billions of digits, so evaluating it would hang the engine. The parser now estimates the exact-expansion cost of every expression statically and rejects expressions that exceed the budget before evaluation starts.
The bounds are:
Additional details:
- Power towers and caret chains are folded right-associatively and checked as a whole, so
((9^9)^9999)^9999and9^9^9^9are both caught even though each individual exponent is small. - Concrete-valued calls are evaluated statically. Wrapping a large value in
abs(),factorial(),binomial(), or a numeric constructor does not hide it from the gate:2^abs(-100000)and2^factorial(10000)are both rejected. - All cost comparisons use exact arithmetic, never binary floats, so the estimates are deterministic.
These bounds are far above anything legitimate verification needs —
2^100, factorial(500), and 300-digit results all pass. They only reject expressions whose exact expansion is computationally unbounded.Edge cases
Performance
Next steps
- Logic engine - Verify logical constraints
- Code engine - Verify code correctness
- API reference - Full API documentation