Formulas¶
Formulas are immutable dataclasses; combine them freely, and wrap one in a
Constraint to check it. All of them are importable from
agentltl. The formula table gives a one-line summary
of each.
Building from a string¶
agentltl._parser.parse
¶
parse(text: str) -> Formula
Parse an LTL formula string into an AST Formula node.
Examples:
>>> parse('before("a", "b")')
Before(a='a', b='b')
>>> parse('called("x") & F(called("y"))')
And(left=Called(tool='x'), right=Eventually(operand=Called(tool='y')))
Formula nodes¶
Abstract Syntax Tree for First-Order Linear Temporal Logic formulas.
This module defines the node types that compose an FOLTL formula AST. It extends propositional LTL with first-order quantifiers (∀, ∃) over dynamically-extracted entity domains.
Every node is an immutable (frozen) dataclass that inherits from Formula.
Supported constructs¶
Atomic propositions (leaf nodes)
* Called(tool) – tool appears at least once in the trace
* CalledWith(tool, args) – tool called with specific argument values
* CalledNTimes(tool, n, op) – tool called exactly / at least / at most n times
* Before(a, b) – first occurrence of a precedes first occurrence of b
* After(a, b) – first occurrence of a follows first occurrence of b
* AllBefore(tools, b) – every tool in tools precedes b (multi-input gate)
* BranchCalled(tool, wrong_tool, context)
– correct branch tool called (not wrong one)
Temporal operators (unary / binary)
* Globally(φ) – G φ (φ holds at every position)
* Eventually(φ) – F φ (φ holds at some position)
* Next(φ) – X φ (φ holds at the next position)
* Until(φ, ψ) – φ U ψ (φ holds until ψ holds)
* WeakUntil(φ, ψ) – φ W ψ (φ holds until ψ, or forever)
* Release(φ, ψ) – φ R ψ (dual of Until)
Logical connectives
* Not(φ) – ¬ φ
* And(φ, ψ) – φ ∧ ψ
* Or(φ, ψ) – φ ∨ ψ
* Implies(φ, ψ) – φ → ψ
Special / convenience
* Predicate(fn, desc) – arbitrary Python callable evaluated with trace context
* AtPosition(index, φ)– φ holds at a specific trace position
First-order quantifiers (FOLTL extension)
* Var(name) – variable placeholder, bound by ForAll / Exists
* ForAll(var, domain, body) – ∀x ∈ D. φ(x)
* Exists(var, domain, body) – ∃x ∈ D. φ(x)
Substitution
* substitute(formula, bindings) – replace Var references with concrete values
All nodes support __str__ for human-readable syntax export and
__repr__ for debugging.
Called
dataclass
¶
Now
dataclass
¶
Bases: Formula
True iff the call AT THE CURRENT POSITION is tool.
The step-local counterpart of Called, which is a fact about the
whole trace (called("x") holds at every position once x appears
anywhere). Inside temporal operators that is rarely what a rule means:
G(called("deploy") -> X(...)) fires at every step of a run that
deploys once. G(now("deploy") -> X(...)) fires at the deploy step only.
CalledWith
dataclass
¶
Bases: Formula
True iff tool was called with arguments matching expected_args.
expected_args is a dict; the check passes when every key in expected_args is present in the actual call arguments and the values are equal. Extra keys in the actual call are allowed.
CalledNTimes
dataclass
¶
Bases: Formula
True iff the number of calls to tool satisfies the comparison.
op is one of "==", ">=", "<=", ">", "<".
Before
dataclass
¶
After
dataclass
¶
AllBefore
dataclass
¶
Bases: Formula
True iff every tool in tools appears before the first occurrence of target.
This captures a multi-input gate constraint: several parallel steps must all complete before a convergence point.
BranchCalled
dataclass
¶
Bases: Formula
True iff the correct_tool was called (and optionally wrong_tool was NOT called).
Captures branch / conditional constraints where the agent must choose the
correct tool path. context is a human-readable description of the
branch condition (e.g. "risk_score > 0.7").
CalledWithResult
dataclass
¶
Bases: Formula
True iff tool was called and its return value matches expected_result.
expected_args optionally restricts which call instances are considered
(same subset-match semantics as CalledWith).
expected_result may be:
- a plain value — checked for equality against the recorded result,
- a
dict— checked as a subset of the result dict (partial match), - a
Var— bound by an enclosing quantifier.
The result is read from the "tool_result" (or "result") key in the
raw metrics["tool_calls"] entry.
InstanceBefore
dataclass
¶
Bases: Formula
True iff the n-th occurrence of tool_a precedes the m-th occurrence of tool_b.
Both n and m are 1-based. If either tool has fewer occurrences than required the constraint fails with a clear message.
Example:
InstanceBefore("read_file", 2, "write_file", 1)
# "the second read_file happened before the first write_file"
CalledInOrder
dataclass
¶
Bases: Formula
True iff tools appear as a subsequence of the trace in the given order.
The tools need not be consecutive — only their relative order matters. Duplicate tool names are allowed; each match consumes one occurrence.
Example:
CalledInOrder(["validate", "process", "commit"])
# validate appears somewhere before process, which appears before commit
WithinSteps
dataclass
¶
Bases: Formula
True iff tool_b first occurs within n steps after the first occurrence of tool_a.
Both tools must be present in the trace, and tool_b must appear at or
after tool_a. The distance is position(b) − position(a); the
constraint passes when distance <= n.
Example:
WithinSteps("alert", "acknowledge", 3)
# acknowledge must be called within 3 steps of alert
Until
dataclass
¶
WeakUntil
dataclass
¶
Release
dataclass
¶
Predicate
dataclass
¶
Bases: Formula
Arbitrary predicate evaluated against the full trace context.
fn receives (trace: Trace, position: int) and must return bool.
description is a human-readable label used in reports and __str__.
runtime_safe is consulted by the static runtime-safety classifier in
agentltl.runtime_safety. Set it to True to declare that
a violation of fn is detectable at any finite prefix (a safety
property) or False to declare that the property is unbounded
liveness. None (the default) means the framework defers to the
user; the classifier reports AMBIGUOUS and the registration-time
hook stays silent.
Because callables are not hashable in general, equality falls back to identity comparison.
Since
dataclass
¶
CallPattern
¶
What Matches asks about a call. Harnesses supply their own (globs, paths,
files that exist); ToolPattern is the plain one.
match(name, args) returns True, False, or None for "maybe": the call's arguments
are only known when it runs (xargs rm). A pattern whose match takes a third
argument also gets the recorded call (a ToolCall: its result, and raw with
the exit status a harness recorded as status). bind(bindings) returns the pattern
with quantifier variables (Var values) replaced. tools names the tools it
can match, for linting.
ToolPattern
¶
Bases: CallPattern
A call to one of tools whose arguments include args (equal values; a
Var value is bound by an enclosing quantifier).
Matches
dataclass
¶
Bases: Formula
The call at the current position matches pattern.
maybe says how a call that only may match (the pattern answered None) counts: True where counting it as a match is the cautious reading (a prohibited call), False where not counting it is (a call that would satisfy an obligation). Either way the failure it causes is marked as resting on a possible match.
Var
dataclass
¶
Variable reference, bound by a ForAll or Exists quantifier.
Appears inside CalledWith.expected_args values. NOT a Formula —
it is a placeholder resolved by substitute before evaluation.
ForAll
dataclass
¶
ForAll(var: str, domain: Any, body: Formula, description: str = '')
Bases: Formula
∀x ∈ domain. body(x)
True iff body holds for every entity returned by domain. Empty domain → vacuously true (standard FOL semantics).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
var
|
str
|
Name of the bound variable. |
required |
domain
|
Any
|
|
required |
body
|
Formula
|
Formula template containing |
required |
description
|
str
|
Human-readable label for the domain (used in |
''
|
Exists
dataclass
¶
Exists(var: str, domain: Any, body: Formula, description: str = '')
Bases: Formula
∃x ∈ domain. body(x)
True iff body holds for at least one entity returned by domain. Empty domain → false (standard FOL semantics).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
var
|
str
|
Name of the bound variable. |
required |
domain
|
Any
|
|
required |
body
|
Formula
|
Formula template containing |
required |
description
|
str
|
Human-readable label for the domain (used in |
''
|
eventually
¶
eventually(tool_or_formula: Union[str, Formula]) -> Eventually
F(called("tool")) when given a string, F(φ) otherwise.
always
¶
G(called("tool")) when given a string, G(φ) otherwise.
substitute
¶
Replace all Var references in formula with concrete values.
Walks the AST recursively. Only CalledWith.expected_args values are
checked for Var instances (variables don't appear in tool-name positions).
Quantifier scoping is respected: if an inner ForAll / Exists binds
the same variable name, that inner scope shadows the outer binding.
Raises:
| Type | Description |
|---|---|
ValueError
|
If a standalone |