python3 --version to check), an API key for any LLM chat/API you already have access to (exact setup — check your provider's documentation), and a small C or Python project you want to check for logic errors.This guide shows a repeatable workflow: prepare code, ask an LLM to find logical flaws, then confirm each finding with a deterministic tool (compiler warnings, assertions, a test run). The LLM proposes; the verifier confirms.

python3 --version must print Python 3.10 or higher.sudo apt install build-essential (Ubuntu) or xcode-select --install (macOS).~/verify-demo.What to do: Create a demo file with an intentional logic bug, then run your compiler/linter to get a baseline of what deterministic tools already catch.
mkdir -p ~/verify-demo && cd ~/verify-demo
cat > discount.c <<'EOF'
#include <stdio.h>
#include <stdbool.h>
int final_price(int price, bool is_vip, int coupon_pct) {
int discount = 0;
if (is_vip) discount += 15;
discount += coupon_pct; // bug: discounts can stack over 100%
return price - price * discount / 100;
}
int main(void) {
printf("%d\n", final_price(1000, true, 90)); // expect a sane price
return 0;
}
EOF
gcc -Wall -Wextra -fsanitize=undefined,address -o discount discount.c && ./discount
Why: The LLM step later is only useful if you know what the machine already found. Compiler warnings and sanitizers (built-in runtime checks for undefined behavior) are deterministic; anything they miss is the target for the LLM pass. This compiles cleanly — the bug (VIP 15% + coupon 90% = 105% discount, so the output is a negative price) is purely logical, which is exactly the class of errors LLMs are good at surfacing.
How to check: The command prints a number. If it prints -50, you reproduced the bug: a price of 1000 became negative. gcc prints no warnings — that silence is your baseline.
Example: expected output:
-50

What to do: Send the code to the LLM with a structured prompt that forces it to enumerate logic errors with line references and severity, instead of writing a vague review.
Save this as prompt.txt:
You are a formal-verification assistant. Analyze the code below.
For EACH issue output exactly:
- ID (L1, L2, ...)
- line number
- class: overflow / boundary / off-by-one / invariant violation / unreachable / other
- severity: high / medium / low
- one-line explanation of the violated property
Do not comment on style. If a function is correct, say "no issues".
Code:
<paste discount.c here>
Then either paste it into your LLM's web chat, or, if you use an API, call it from Python (endpoint, model name and auth header depend on your provider — check the provider's documentation):
import os, sys, urllib.request
code = open("discount.c").read()
prompt = open("prompt.txt").read().replace("<paste discount.c here>", code)
body = os.environ["LLM_REQUEST_JSON_TEMPLATE"].replace("{PROMPT}", prompt.replace("\\", "\\\\").replace('"', '\\"'))
req = urllib.request.Request(
os.environ["LLM_API_URL"], # e.g. https://<your-provider>/v1/... — see provider docs
data=body.encode(),
headers={"Content-Type": "application/json",
"Authorization": "Bearer " + os.environ["LLM_API_KEY"]},
)
print(urllib.request.urlopen(req).read().decode())
Why: The structured output format (ID + line + class + property) is what makes the next step possible: each finding becomes a checkable claim. A free-form "this code looks risky" answer cannot be verified. Formal verification means proving a property (e.g., "output price is always between 0 and input price"); the LLM proposes candidate violated properties, and you confirm them.
How to check: The model's answer contains at least one finding of class boundary or invariant violation pointing at the discount += coupon_pct; line, with severity high. If the answer is prose without IDs and line numbers, re-send the prompt — the format instruction was ignored.
Example: a correct answer looks like:
L1 | line 7 | invariant violation | high | discount can exceed 100, so final_price returns a negative value
If you also want the LLM to fix the code, ask in a second message: "Rewrite final_price so the total discount is capped at 100 and the result is never negative." Keep the fix as a separate diff — never let the model silently rewrite the whole file. For a broader look at wiring an LLM into your own services, see How to add AI to your home server: self-hosted services guide.

What to do: Turn each LLM finding into an executable check. For finding L1, add an assertion that encodes the violated property, rebuild, and run:
cat > check.c <<'EOF'
#include <assert.h>
#include <stdbool.h>
int final_price_fixed(int price, bool is_vip, int coupon_pct) {
int discount = (is_vip ? 15 : 0) + coupon_pct;
if (discount > 100) discount = 100;
int result = price - price * discount / 100;
assert(result >= 0 && result <= price); // the property the LLM claimed was violated
return result;
}
int main(void) {
assert(final_price_fixed(1000, true, 90) == 100); // 100% discount cap
assert(final_price_fixed(1000, false, 0) == 1000);
assert(final_price_fixed(0, true, 0) == 0);
return 0;
}
EOF
gcc -Wall -Wextra -o check check.c && ./check && echo "ALL PROPERTIES HOLD"
Why: This is the core rule of the whole workflow: an LLM finding is a hypothesis until an assertion, a test, or a sanitizer run confirms it. The assert line is the formal property written in code; if the fixed function ever returns a negative price, the program aborts with the exact line number.
How to check: The last line of output is ALL PROPERTIES HOLD, and ./check exits with status 0 (echo $? prints 0). If an assertion fires, you will see check: check.c:9: final_price_fixed: Assertion 'result >= 0 && result <= price' failed. — that means the property is still violated and the fix is wrong.
Example: full expected output:
ALL PROPERTIES HOLD
Repeat this loop (LLM proposes → you encode the property → assertion/test confirms) for every finding. Two or three iterations usually clear a small file.
Проблема. The LLM answer is generic prose with no IDs or line numbers
L1 | line 7 | … lines.Проблема. The LLM reports a bug that does not exist (false positive)
price * discount", but your inputs are small and ./check passes../check; keep only findings that survive the assertion run.Проблема. gcc: error: unrecognized command line option '-fsanitize=undefined,address'
gcc: error: unrecognized command line option '-fsanitize=undefined,address'gcc -Wall -Wextra -o discount discount.c && ./discount. Sanitizer availability depends on your toolchain — check your compiler's documentation.The workflow is a loop: prepare a baseline with deterministic tools, have the LLM enumerate candidate logic errors as structured claims, then convert each claim into an assertion or test that either holds or fails. Next, scale it by running the same prompt over every file in your repository and keeping a check_*.c file per verified property.
Can an LLM replace a real formal verifier (proof tools)?
No. In this workflow the LLM only proposes candidate properties and bugs; the proof obligation is discharged by assertions, tests and sanitizers. Treat model output as unverified hypotheses.
Which model should I use?
Any LLM you already have access to will follow the structured prompt. Model names, versions and pricing change frequently — check your provider's documentation for the current options.
Does this work for languages other than C?
Yes. The loop is language-agnostic: use your language's assertion mechanism (assert in Python, panic/tests in Go, etc.) and its compiler/linter for the baseline step.
How do I know a finding is a true positive?
Only by an executable check: if the assertion or test you wrote for that property fails on the original code and passes after the fix, the finding was real.