Skip to main content

g6-model-checking

Use when a design has states and steps (a protocol, a lock, a retry loop, a workflow, a state machine, concurrent or distributed steps) and you are about to say it never reaches a bad state, never deadlocks or can always reach a goal, from tests or a reading that tried only some orders of events.

Source facts

Repository
basicisabc1994/g6solver
Last source activity
October 30, 2026 at 10:47
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
g6-model-checking
description
Use when a design has states and steps (a protocol, a lock, a retry loop, a workflow, a state machine, concurrent or distributed steps) and you are about to say it never reaches a bad state, never deadlocks or can always reach a goal, from tests or a reading that tried only some orders of events.
# G6 model checking Checks a state machine for broken invariants, deadlocks and reachable goals: symbolic models in SMT-LIB checked with Z3 in process, and TLA+ specs through TLC and SANY out of process. Every symbolic trace and every "nothing found" is re-checked on fresh solvers before it is reported. ## When to use it - You are about to write "this lock cannot deadlock", "the counter never exceeds N" or "every request is eventually served" about a design. - A bug appears only under some interleaving and tests have not found it. - You want a concrete trace from an initial state to the bad state, not a suspicion. - You have a TLA+ spec and want TLC's verdict, or a syntax and level check from SANY. ## When not to use it - A single logical step with no notion of time: use `formal`. - Safety bounds and LTL properties on an agent model: use `csf`. - Whether the code's tests pass: run them, and report through `coding_agent_gate`. - A schedule or route to optimise: use `optimisation`. ## Ops - `check_invariant`: bounded model checking to `depth` steps (at most 50); a state breaking an invariant with its trace from an initial state, or none on paths of up to that depth, which is all it claims. - `prove_invariant`: k-induction up to `depth` as the largest k; the invariants inductive at k, a counterexample from the initial states, or "not inductive at k" with an induction-step trace that need not start in a reachable state. - `check_deadlock`: a reachable state with no successor, with its trace, within the bound. - `reachable`: a trace to a state meeting `goal`, or none within the bound. - `tlc_check`: TLC's verdict on a TLA+ `spec`, given either `config` text or the fields `specification`, `invariants`, `properties` and `constants` (not both), with `check_deadlock` and `workers`, and its counts and trace (sensitive: runs TLC, from the TLA+ tools on Java, inside G6's operating-system boundary where one is ready and as you elsewhere; a spec can write files through TLC's own modules, such as `Json`). - `sany_parse`: SANY's syntax, semantic and level check of `spec`, nothing model-checked (sensitive: runs SANY on Java, inside G6's boundary where one is ready, as you elsewhere). ## Reading the result A symbolic `model` is `{"variables": {"x": "Int"}, "init": term, "transition": term, "invariants": {name: term}}`, all SMT-LIB read through `formal`'s closed grammar; the next-state copy of `x` is `next_x`. The reply gives `verdict`, `trace` (states, step by step), `trace_kind`, `k` and `recheck` (each re-check query and its answer). - `qualified-draft`: `G6_E_MODEL_CHECK_BOUNDED` (nothing found on paths up to the depth only), `G6_E_MODEL_CHECK_ALL_PATHS_END` (every path ended within it), `G6_E_MODEL_CHECK_K_INDUCTION` (the `verdict` field says `k_inductive`, with the k in `k`), `G6_E_MODEL_CHECK_TRACE_FOUND` (a witness for `reachable`), `G6_E_MODEL_CHECK_TLC_NO_ERROR` (TLC's own verdict), `G6_E_MODEL_CHECK_SANY_NO_ERROR` (a clean parse; nothing model-checked). These are on `warning_card.code`; the result's own `code` is empty. Nothing here is `verified`. - `blocked-escalated`, the trace kept in `trace`: `G6_E_MODEL_CHECK_VIOLATION`, `G6_E_MODEL_CHECK_DEADLOCK`, `G6_E_MODEL_CHECK_NOT_INDUCTIVE`, `G6_E_MODEL_CHECK_TLC_INVARIANT_VIOLATED` and TLC's other violations. - `blocked-escalated` with no verdict: `G6_E_MODEL_CHECK_VACUOUS_INIT` and `G6_E_MODEL_CHECK_VACUOUS_TRANSITION` (the model has no initial state, or no state has a successor), `G6_E_MODEL_CHECK_CERTIFICATE_REFUTED`, a missing jar or Java, a digest mismatch, a time limit. ## Alone and composed Alone, over MCP: `g6_invoke("model_checking", "check_invariant", params_json)`. From Python: `from g6.model_checking import check_invariant, prove_invariant, tlc_check`; the TLA+ ops also run from `get_registry().invoke("model_checking", "tlc_check", {...})`. Composed: - It requires `formal`: its terms go through `formal`'s grammar to Z3. Decide a single step's logic there first. - Run `check_invariant` for a counterexample, then `prove_invariant` for the depth-free claim; give `coding_agent_gate`'s `verify_answer` the reply as an evidence item of kind `log` before reporting a design as sound (it counts as caller-declared). ## Failure modes it catches - `rare_interleaving` (a design that fails only in a rare order of events nobody tried): the solver searches every path up to the depth, not the orders a test happened to run, and returns the trace that breaks an invariant or deadlocks. Each trace is re-checked state by state (state 0 satisfies init, each step the transition, the last state breaks the invariant), and every claim that nothing was found, that every path ends or that k-induction holds is decided again in a second Z3 context. ## Blind spots - The model is yours. If the SMT-LIB or the TLA+ spec does not match the code, the verdict is about the model, not the code; G6 does not extract a model from source. - `check_invariant`, `check_deadlock` and `reachable` claim nothing past `depth`. Only `prove_invariant` reaches beyond, and "not inductive at k" is not a counterexample: the invariant may hold yet need strengthening. - No CTL, LTL or fairness on symbolic models; TLC checks temporal properties of TLA+ specs instead. - TLC's "no error" is TLC's own verdict, not re-checked by G6, and holds only for the constants and state space TLC explored. - Z3 can answer unknown on non-linear arithmetic (`G6_E_MODEL_CHECK_SOLVER_UNKNOWN`); that is a refusal, not a pass. ## What it needs - `pip install "g6[formal] @ git+https://github.com/basicisabc1994/g6solver"` for Z3, which the symbolic ops use. Without it they are `blocked-escalated` with `G6_E_MODEL_CHECK_SOLVER_UNAVAILABLE`. - Java, only for `tlc_check` and `sany_parse`: Java 11 or later on PATH, or `G6_JAVA` naming the java program. Then download `tla2tools.jar` yourself from the TLA+ project's releases (G6 is tested on v1.7.4), check it, set `G6_TLA2TOOLS_JAR` to its path and `G6_TLA2TOOLS_SHA256` to its SHA-256. G6 never downloads it and runs only the jar whose digest you pinned. Over MCP these two ops run only when the person running the server lists them (or `model_checking`) in `G6_MCP_SENSITIVE`; an agent never edits that setting to grant itself one. ## Examples ```g6-example op=check_invariant {"model": {"variables": {"x": "Int"}, "init": "(= x 0)", "transition": "(= next_x (+ x 1))", "invariants": {"small": "(<= x 3)"}}, "depth": 10} ``` The reply is `blocked-escalated` with `G6_E_MODEL_CHECK_VIOLATION`: `violated` is `["small"]` and `trace` runs x = 0, 1, 2, 3, 4, with `recheck.passed` true. ```g6-example op=prove_invariant {"model": {"variables": {"x": "Int"}, "init": "(= x 0)", "transition": "(= next_x (+ x 1))", "invariants": {"natural": "(>= x 0)"}}} ``` `qualified-draft` with `G6_E_MODEL_CHECK_K_INDUCTION`, `verdict` `k_inductive` and `k` 1: the invariant is 1-inductive, so it holds in every reachable state, not only up to a depth, as a solver decision on this model. ## The four open problems - **Learning signals.** Not yet: it writes nothing to a run ledger itself. Each catch is a labelled result with its own `G6_E_MODEL_CHECK_*` code, its trace in `trace` and the trace's re-check in `evidence`; recording it is the caller's step today. - **Evaluators the model cannot influence.** The search is Z3's or TLC's, and the re-checks are the package's own queries in a second Z3 context; TLC runs only from a jar pinned by its SHA-256. No model call runs anywhere in the package; the caller writes the state model, never the check. - **Transfer by failure mode.** The method belongs to state machines, not to a task: any model the grammar can write is searched the same way. Not yet: its catch rate on task families it was not built on has not been measured. - **Enough held-out cases.** Not yet: its own catch rate and false-alarm rate are not judged by a sequential test. A repeated workflow that uses it can be tuned with `g6.metaharness`, whose gate judges a change on held-out cases.
View on GitHub