quint-co/quint-llm-kit/quint-lang

Claude Code and agent skill by quint-co/quint-llm-kit

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Quint language and CLI reference — the expert on Quint syntax, operators, types, `basicSpells`, the toolchain (typecheck/run/test/verify), and how to read simulation and counterexample output. Use when writing or debugging the contents of a `.qnt` file, fixing a typecheck/parse error, looking up an operator or idiom, analyzing an invariant violation or counterexample trace, or optimizing state-space exploration. This is for working IN Quint at the language level — not for analyzing or running TLA+/TLC itself. For building a NEW model end-to-end from some source — including translating a TLA+ spec into Quint, or modeling code/requirements/an idea — use the quint-modeling skill, which owns that workflow and consults this reference for syntax. Keywords: quint, syntax, operators, typecheck, model checking, counterexample, basicSpells, CLI, specification language.

Install

npx vskill@latest install quint-co/quint-llm-kit/quint-lang

SKILL.md

Quint Language Reference

Quint is an executable specification language for complex systems, developed by Informal Systems. It compiles to TLA+ and supports simulation and model checking.

Module structure

quint
module MyProtocol {
// type aliases, constants, state, actions, properties
}

Modules can import others:

quint
import Voting.* // all definitions
import Voting(quorum) // specific definition
import Voting as V // namespace alias

Types

TypeDescriptionExample
intIntegers-1, 0, 42
boolBooleanstrue, false
strStrings"hello"
Set[T]Finite setSet(1, 2, 3)
List[T]Ordered sequenceList(1, 2, 3)
K -> VKey-value map (type is K -> V, not Map[K, V])value: Map("a" -> 1)
(T1, T2)Tuple(1, "x")
{ f: T, g: U }Record{ x: 1, ok: true }
`T \U`Sum (variant)(use type alias)

Type aliases:

quint
type NodeId = int
type Phase = Idle | Propose | Vote | Commit // enum — prefer over string literals

Definitions

quint
// Module parameter — fixed at instantiation, not a state variable
const N: int
const Nodes: Set[str]
// Pure function — no state access, usable anywhere
pure def max(a: int, b: int): int = if (a > b) a else b
// Stateful operator — can read vars, takes arguments (unlike val)
def isActive(n: str): bool = active.contains(n)
// State-reading value — can read vars, no arguments
val quorum: bool = votes.size() * 2 > nodes.size()
// Compile-time constant
pure val N: int = 4
val threshold: int = N / 2 + 1

const vs pure val: const is a module parameter bound at instantiation (import A(N = 3)); pure val is a fixed expression computed once. def vs val: def takes arguments; val does not.


State variables

quint
type LocalState = {
leader: int,
phase: Phase, // enum (see Type aliases) — prefer over a bare str
votes: Set[int],
log: List[str],
}
var localState: LocalState // cohesive local protocol state
var peers: int -> str // independent concern (peer metadata)

State variables can only be read in val definitions and actions; they cannot be read in pure def.


Actions

Actions describe state transitions. They return bool — true if the action fires.

quint
action init: bool = all {
leader' = 0,
phase' = Idle,
votes' = Set(),
log' = List(),
state' = Map(),
}
action propose(node: int): bool = all {
phase == Idle,
node > 0,
leader' = node,
phase' = Propose,
votes' = votes,
log' = log,
state' = state,
}

Key rules:

  • Every var must be assigned in every action (use x' = x to leave unchanged).
  • all { ... } — all sub-expressions must hold (conjunction). Guards are plain boolean expressions inside all { }.
  • any { ... } — at least one must hold (disjunction); the REPL picks non-deterministically.

Non-determinism

quint
action step: bool = any {
propose(1),
propose(2),
vote,
timeout,
}
// Non-deterministic choice from a set
action deliverMessage: bool = {
nondet msg = pending.oneOf()
all {
pending.size() > 0,
delivered' = delivered.union(Set(msg)),
pending' = pending.exclude(Set(msg)),
// ... other vars unchanged
}
}

Set operators

quint
Set(1, 2, 3).contains(2) // true
Set(1, 2).union(Set(2, 3)) // Set(1, 2, 3)
Set(1, 2, 3).intersect(Set(2, 3)) // Set(2, 3)
Set(1, 2, 3).exclude(Set(2)) // Set(1, 3)
Set(1, 2, 3).filter(x => x > 1) // Set(2, 3)
Set(1, 2, 3).map(x => x * 2) // Set(2, 4, 6)
Set(1, 2, 3).fold(0, (acc, x) => acc + x) // 6
Set(1, 2, 3).size() // 3
Set(1, 2, 3).forall(x => x > 0) // true
Set(1, 2, 3).exists(x => x > 2) // true
1.to(5) // Set(1, 2, 3, 4, 5)
nondet x = Set(1, 2, 3).oneOf() // non-deterministic pick — only valid in nondet bindings

List operators

quint
List(1, 2, 3).head() // 1
List(1, 2, 3).tail() // List(2, 3)
List(1, 2, 3).length() // 3
List(1, 2, 3).nth(1) // 2 (0-indexed)
List(1, 2, 3).append(4) // List(1, 2, 3, 4)
List(1, 2).concat(List(3, 4)) // List(1, 2, 3, 4)
List(1, 2, 3).foldl(0, (acc, x) => acc + x) // 6
List(1, 2, 3).select(x => x > 1) // List(2, 3)

Map operators

quint
Map("a" -> 1, "b" -> 2).get("a") // 1
Map("a" -> 1).put("b", 2) // Map("a" -> 1, "b" -> 2)
Map("a" -> 1, "b" -> 2).keys() // Set("a", "b")
Set(1, 2, 3).mapBy(k => k * 2) // Map(1 -> 2, 2 -> 4, 3 -> 6) — set of keys → map

Records

Records group related fields into a named type. They are the primary tool for modelling structured state in Quint.

Type aliases for records
quint
type NodeState = {
phase: Phase, // enum: Idle | Propose | Vote | Commit
voted: bool,
log: List[int],
}
type Message = {
from: int,
to: int,
round: int,
payload: str,
}
Creating and accessing
quint
val n: NodeState = { phase: Idle, voted: false, log: List() }
n.phase // Idle
n.voted // false
Updating (immutable — returns a new record)
quint
{ ...n, phase: Propose } // ✅ preferred — idiomatic, handles multiple fields
{ ...n, voted: true, phase: Vote } // ✅ multiple fields at once
n.with("phase", Propose) // ⚠️ valid but non-idiomatic — field name is a string literal
Records as state — when to group variables

TLA+ specs typically flatten all state into independent top-level variables. Quint's type system lets you group them. When fields describe one cohesive local state, make a record type and use a single state variable of that type.

Group into a record when:

  • They represent the local state of a single actor (e.g. one node's phase + log + vote)
  • They are always passed together as function arguments
  • An invariant relates multiple fields of the same conceptual entity

Keep flat when:

  • The variables represent distinct concerns that change independently
  • The component is simple and grouping adds no clarity
  • The variables are intentionally in different ownership/lifecycle domains
Example: preferred grouped local state vs. anti-pattern

Preferred (cohesive local state):

quint
type LocalState = {
id: int,
phase: Phase,
est1: int,
est2: Option[int], // Option is from basicSpells, not built in — see Basic spells below
round: int,
crashed: bool,
leader: int,
received_messages: Set[Message],
}
var localState: LocalState

Avoid for cohesive local state:

quint
var id: int
var phase: Phase
var est1: int
var est2: Option[int]
var round: int
var crashed: bool
var leader: int
var received_messages: Set[Message]

For N actors, use a map of grouped records:

quint
type LocalState = { phase: Phase, votedFor: int, log: List[int] }
var nodes: int -> LocalState
action commit(id: int): bool = {
val node = nodes.get(id)
all {
node.phase == Vote,
nodes' = nodes.put(id, {...node, phase: Commit}),
}
}
Nested records
quint
type ClusterState = {
nodes: int -> NodeState,
leader: int,
epoch: int,
}
var cluster: ClusterState
// Read nested field:
cluster.nodes.get(1).phase
// Update nested field (must rebuild from the inside out):
val updated = {...cluster.nodes.get(1), phase: Commit}
cluster' = {...cluster, nodes: cluster.nodes.put(1, updated)}
Records in sets (messages, events)
quint
var inFlight: Set[Message]
action send(src: int, dst: int, r: int, p: str): bool = all {
inFlight' = inFlight.union(Set({ from: src, to: dst, round: r, payload: p })),
// ...
}
// Filter by field:
inFlight.filter(m => m.to == nodeId)
inFlight.exists(m => m.round == currentRound and m.payload == "vote")

Sum types

Sum types (variants) represent a value that can be one of several distinct cases.

quint
type Action =
| Propose({ value: int, proposer: int })
| Vote({ value: int, voter: int })
| Decide({ value: int })

Each variant has a named constructor and carries one payload. A constructor takes exactly one argument — wrap multiple fields in a record (as above) or a tuple.

Construct a value by calling the constructor:

quint
val a: Action = Propose({ value: 1, proposer: 2 })

Pattern-match with match, binding the payload:

quint
pure def describeAction(a: Action): str =
match a {
| Propose(p) => "proposal"
| Vote(v) => "vote"
| Decide(d) => "decision"
}

Use _ to ignore the payload when you only care which variant it is:

quint
match a {
| Propose(_) => "proposal"
| _ => "other"
}

Use sum types when a message, event, or state can take structurally different forms — not just different values of the same type.


Enum types

Enum types are a special case of sum types where each case has no additional data.

quint
type Phase = Idle | Propose | Vote | Commit
var phase: Phase
if (phase == Propose) { ... }

Variable grouping — decision guide

Before writing var declarations, answer these questions for each candidate group:

QuestionGroup → record if...Keep flat if...
Do these vars always change together?Yes, in most actionsNo, they're independent
Do they describe the same entity?Same node / same message / same roundDifferent concerns
Is there one instance or N instances?Either one or N (group if cohesive; for N use Id -> RecordType)Flat only when concerns are truly independent
Do invariants relate them?Invariant spans multiple fields of one entityInvariant uses vars independently

Boolean operators

quint
not(p) // negation — Quint has no ! operator
p and q // conjunction
p or q // disjunction
p implies q // p => q (not(p) or q)
p iff q // p == q for booleans
and { p1, p2, p3 } // block form — equivalent to p1 and p2 and p3
or { p1, p2, p3 } // block form — at least one must hold

and { } and or { } are the same operators as all { } and any { } in actions — use whichever reads more naturally in context.


Invariants and temporal properties

quint
// Safety invariant — must hold in every reachable state
// @invariant
val noDuplicateLeader: bool =
leaders.size() <= 1
// Temporal property — evaluated over traces
// @temporal
temporal eventualProgress: bool =
eventually(committed.size() > 0)
// Temporal operators
eventually(p) // p holds in some future state
always(p) // p holds in all future states
p.implies(q) // p => q

Assume

quint
assume nodeCountPositive = N > 0
assume quorumMajority = 2 * quorum > N

An assume states a premise about constants, but it is not enforced — a violated assume is silently ignored by quint typecheck, quint run, and quint verify (none of them flags it). It is documentation, not a checked constraint. To actually check a condition on constants, write a run test that asserts it (it executes and fails when the condition is false):

quint
run quorumAssumptionTest = all {
2 * quorum > N,
N > 0,
}

Run it with quint test; the test fails (reporting which conjunct broke) if a constant assignment violates the condition.


Conditional and let

quint
if (x > 0) "positive" else "non-positive"
val result = {
val doubled = x * 2
doubled + 1
}

REPL usage

Prefer CLI commands (quint typecheck, quint run, quint test, quint verify) for all validation and execution tasks. Open the REPL (quint or quint -r spec.qnt::ModuleName) only when you need expression-level interaction the CLI does not provide.

Type inspection:

>>> :type myExpression

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
101
Repository forks
12
7-day trend score
23

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/quint-co/quint-llm-kit/quint-lang/badge)](https://verified-skill.com/skills/quint-co/quint-llm-kit/quint-lang)

Something wrong with this skill or its page? Tell us.