Infers likely loop invariants and function contracts by observing execution traces, synthesizing candidates, and checking them inductively. Use when a verifier rejects a loop because the invariant is missing or too weak, when a Daikon-style tool is needed, or before translating code to a verification language.
Install with Tessl CLI
npx tessl i github:santosomar/general-secure-coding-agent-skills --skill invariant-inference97
Quality
96%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
A loop invariant is what stays true every time you hit the loop head. Verifiers need it stated; programmers rarely state it. This skill guesses it from evidence.
| Route | How it works | Good at | Bad at |
|---|---|---|---|
| Dynamic (Daikon-style) | Run the code, record state at loop head, generalize patterns | Concrete relationships in traces | Generalization — might be coincidence |
| Static (template-based) | Guess invariants of form ax + by ≤ c, solve for a,b,c | Sound by construction | Limited to the template shapes |
Do both: dynamic finds candidates fast; static checks them.
Template library (what to look for in the dumps):
| Template | Example |
|---|---|
| Constant | n == 10 in every observation |
| Range | 0 <= i <= n |
| Linear relation | i + j == n, 2*i == k |
| Ordering | i < j, a[i] <= a[j] |
| Membership | x in {0, 1, 2} |
| Collection property | sorted(a[:i]), sum(a[:i]) == s |
| Implication | flag => (x > 0) |
(iter, locals) at loop head.x == 5 in every trace but your test inputs all had n == 5 — it's not invariant, it's your test bias. Vary n.I: does I ∧ guard ∧ body ⟹ I'? Use an SMT solver or just plug into the verifier.I ∧ ¬guard ⟹ postcondition? If not, I is true but useless — you need a stronger one.Loop:
def find_max(a):
m = a[0]
i = 1
while i < len(a):
if a[i] > m:
m = a[i]
i += 1
return m # post: m == max(a)Traces (input a = [3, 1, 4, 1, 5]):
| iter | i | m | a[:i] |
|---|---|---|---|
| 0 | 1 | 3 | [3] |
| 1 | 2 | 3 | [3, 1] |
| 2 | 3 | 4 | [3, 1, 4] |
| 3 | 4 | 4 | [3, 1, 4, 1] |
| 4 | 5 | 5 | [3,1,4,1,5] |
Candidate patterns (mined):
1 <= i <= len(a) ✓ all rowsm in a ✓ all rowsm == max(a[:i]) ✓ all rows — this is the onem >= a[0] ✓ all rows — true but weaker than the aboveInductive check on m == max(a[:i]):
i=1, m=a[0] → max(a[:1]) = a[0] = m. ✓m == max(a[:i]). Body sets m' = max(m, a[i]) and i' = i+1. Then m' = max(max(a[:i]), a[i]) = max(a[:i+1]) = max(a[:i']). ✓i == len(a). Then m == max(a[:len(a)]) == max(a). ✓Invariant: 1 <= i <= len(a) ∧ m == max(a[:i]).
a[i] * a[j] < n — nonlinear. → abstract-invariant-generator with a polynomial domain, or hand-write.true is inductive; it proves nothing.i in {1, 2, 3, 4, 5} holds on your one trace — but the real invariant is 1 <= i <= len(a). Prefer the most general candidate that survives.## Candidates (from traces)
<template> : <instantiation> — held on <N>/<N> observations
## Inductive check
<candidate> — <✓ inductive | ✗ fails: <counterexample>>
## Sufficient for postcondition
<candidate> — <✓ | ✗ too weak, missing: <what>>
## Recommended invariant
<the winner — conjunction of surviving, sufficient, minimal candidates>47d56bb
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.