Use ponens programmatically
You'll end up with policy compliance you can drive from your own tool — a machine-readable checker you can call on every change, in-process evaluation from Python, and a live compliance state that updates as work happens. No server: the checker is pure, local, and deterministic.
ponens evaluates LTLf policies over a
trace. The same evaluator that powers ponens trace check is
exposed two ways — a JSON-emitting CLI and a Python function — so a build step, an editor
extension, or a CI job can ask "does this trace comply?" and act on the answer.
Install or update — the latest release is v1.7.1:
pip install -U ponens # or: uv pip install -U ponens / pipx upgrade ponens Grab a trace to try this on
ponens ships real sample traces — machine-checked proofs, decompositions, conformance runs. List them and pull one down, so every command below runs against an actual trace:
ponens demos list
ponens demos get stripe_v1_1.json -o trace.json 1. Check a trace, get JSON back
--json makes the checker machine-readable: it prints the
policy_evaluation records (one per policy) and nothing else, so you can pipe it
straight into your tool.
ponens trace check trace.json --json [
{ "policy_id": "formalize_before_verify", "status": "passed", "checked_at_action_id": 42 },
{ "policy_id": "decomposition_drives_tests", "status": "passed", "checked_at_action_id": 42 },
{ "policy_id": "reasoning_required_for_high_stakes", "status": "failed", "checked_at_action_id": 42 }
] Each record carries the policy_id, a status
(passed / failed / unknown / not_applicable),
the action position it was checked at, and an optional note. A trace with no
policies prints [].
2. Stamp the results into the trace
--write evaluates and writes the policy_evaluations array back into
the trace file in place — so the trace becomes self-describing (it carries both its policies and
their latest results, ready for the viewer or an audit).
ponens trace check trace.json --write 3. Evaluate in-process (Python)
Skip the subprocess entirely — call the evaluator directly. It's a pure function over a trace dict, so you can score policies inside your own pipeline.
from ponens.trace import evaluate_policy, normalize_trace
import json
trace = json.load(open("trace.json"))
normalize_trace(trace) # resolve artifact/output links
for policy in trace["policies"]:
status, note = evaluate_policy(policy, trace)
print(policy["policy_id"], status, note or "") evaluate_policy(policy, trace) returns (status, note). A policy is
just an object with a name, a severity, and an LTLf
formula — you can hand-write one, or pull it from the gallery (next).
4. Connect gallery packs
Discover packs and pull their policies (with formulas) from the configured sources — the
community gallery, or a local one you add to .ponens/sources.toml.
# list available packs
ponens search --type pack --json
# a pack's policies, ready to embed in a trace's "policies" array
ponens search --json | jq '.policies[] | select(.pack == "apply-formal-methods")' Attach the ones you want to your trace's policies list, then run
ponens trace check. That's the whole loop: select packs → embed → check.
The apply-formal-methods pack, for example, requires that
high-stakes code is formally analyzed and that decompositions drive tests.
5. A live per-change compliance loop
Putting it together: re-check on every change and keep a compliance state that's always current. An editor integration or a formal-reasoning agent can call the checker after each change and update its compliance view from the result.
import json, subprocess
def recompute_compliance(trace_path):
"""Call the native checker; return the fresh policy_evaluations."""
out = subprocess.run(
["ponens", "trace", "check", trace_path, "--json"],
capture_output=True, text=True,
).stdout
return json.loads(out or "[]")
# ... on every artifact / commit / save:
evaluations = recompute_compliance("trace.json")
failed = [e for e in evaluations if e["status"] == "failed"]
if failed:
print("policy violations:", [e["policy_id"] for e in failed]) "Where it makes sense": a data-driven high-stakes surface
The high_stakes_path predicate — used by policies like
reasoning_required_for_high_stakes — matches an action's target against the
trace's high_stakes_paths. Set it from whatever decides which code warrants formal
methods (a target scan, a CODEOWNERS-style list, a risk register):
{
"trace_id": "...",
"high_stakes_paths": ["billing/", "auth/", "payments/"],
"policies": [ /* ... */ ],
"actions": [ /* ... */ ]
} Now "apply formal methods where it makes sense" is decided by evidence in the trace, not a hard-coded path list — and every check re-evaluates against the current surface.