Skip to main content
Updated in v5.3.0 (breaking). Every SymbolicVerifier public method now returns a DiagnosticResult instead of an ad-hoc dict. See the migration section for a field-by-field mapping. The Code Engine is the first fully DiagnosticResult-conformant engine and serves as the reference implementation for future engine migrations.
The Code Engine uses CrossHair, a symbolic execution tool for Python, to verify code properties without running it.

Capabilities


Quick start

Every method on SymbolicVerifier returns a DiagnosticResult with the same three layers described in Verification Diagnostics: a Layer 1 agent_message, a Layer 2 developer_fields dict, and a Layer 3 proof_ref (always None for this engine — see Why VERIFIED is never emitted).

The DiagnosticResult contract

All six public methods — verify_code, verify_function_contract, verify_safety_properties, verify_bounded, analyze_complexity, get_verification_budget — return a DiagnosticResult.

Status values

Read the specific reason from result.developer_fields["constraint_id"].

Common developer_fields keys

Every result carries verification_mode so callers can tell a plain symbolic run from a bounded run: Method-specific keys (function counts, loop lists, budget estimates) are documented in the sections below.

Why VERIFIED is never emitted

CrossHair’s search is timeout-bounded, not a completeness proof. “No counterexample found” is not the same as “no counterexample exists,” so a clean run maps to UNVERIFIABLE with constraint_id = "symbolic_verifier.no_counterexample_found" — never to VERIFIED. The DiagnosticResult contract structurally requires a proof_ref for VERIFIED, and this engine has no proof artifact to bind. Downstream policy gates must reject UNVERIFIABLE results for control flow. Treat symbolic-execution output as a bug hunter, not an authority.

Symbolic verification

verify_code() walks the AST, extracts every function with type annotations, and runs CrossHair on each one.
developer_fields on verify_code() results:
verify_code() no longer accepts check_assertions — the parameter was never wired up. The current signature is verify_code(self, code: str) -> DiagnosticResult. Passing extra keyword arguments raises TypeError.

Counterexample: division by zero

Untyped function: fails closed

CrossHair requires type hints. Untyped functions are reported as skipped and unverifiable — the result cannot be is_verified: True.
See Symbolic execution limits for the full fail-closed matrix.

Contract verification

verify_function_contract() injects preconditions as assert statements and re-runs verify_code() against the decorated source.
The returned DiagnosticResult follows the same shape as verify_code(). Read result.developer_fields["issues"] to find failing assertions.

Safety property analysis

verify_safety_properties() scans the AST for division-by-zero and index-out-of-bounds hazards. It always returns UNVERIFIABLE (the check is advisory and does not prove the code is safe) with structured details in developer_fields.
The advisory_checks array carries a safety_properties entry with the same summary counts.

Bounded model checking

verify_bounded() transforms the source to inject loop counters and recursion-depth checks, then calls verify_code() on the rewritten source. Every result from this method sets verification_mode = "bounded_symbolic" and includes the applied bounds plus the underlying complexity analysis.
If the bounded-model transform itself fails, verify_bounded() returns BLOCKED with constraint_id = "symbolic_verifier.bounds_transform_error" instead of silently falling back to the untransformed source. See Bounded model checking for configuration guidance and preset budgets.

Complexity analysis

analyze_complexity() counts loops, direct and mutual recursions, and maximum loop-nesting depth. The result is always UNVERIFIABLE — the analysis is advisory and produces no proof.
Use recommendation to pick sensible bounds for a follow-up verify_bounded() call.

Verification budget

get_verification_budget() estimates the number of symbolic paths CrossHair would explore, so callers can decide whether to attempt verification or fall back to a cheaper check.
Both this method and analyze_complexity() are advisory — treat their output as heuristics, not proof. The result carries an advisory_checks entry with the same summary details.

Migrating from the legacy dict API

Before v5.3.0, SymbolicVerifier methods returned ad-hoc dicts (result.verified, result["issues"], result["complexity"], and so on). v5.3.0 removes those return types entirely — every method now returns a DiagnosticResult. Update your call sites using the map below.

Field cheat sheet

Before

After

Do not gate control flow on is_verified for symbolic-engine results — it is always False. Downstream authority checks must inspect result.proof_ref, which this engine intentionally leaves as None. See Verification Diagnostics for the full 3-layer contract.

Removed parameters

  • verify_code(code, check_assertions=...) — the parameter was never used. Remove it from your call sites; passing it now raises TypeError.

Language support


Next steps