All notable changes to AtpClient are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.7.2] - 2026-10-13
Added
:dirson Isabelle.AtpClient.Isabelle.open_session/1(and every entry point that opens a session) takes:dirs, a list of directories forwarded asdirsto Isabelle'ssession_start, so:sessioncan name a session defined outside the distribution. The paths are the server's view and are passed through verbatim;config :atp_client, :isabelle, dirs: […]sets a default, andconfig_schema/0lists it as a:string_listfield.
[0.7.1] - 2026-10-12
Added
- System names on SystemOnTPTP.
query_system/3,query_selected_systems/3and:default_systemtake a bare name ("Vampire") as well as an identifier ("Vampire---5.0.1"); a name resolves to the latest version the deployment advertises, throughAtpClient.SystemOnTptp.Provers.resolve/2, so callers are not tied to a version string that changes with the next deployment. The result'ssystemnames the version that ran.Provers.systems/0lists every advertised system, tools included;Provers.name/1is the name of an identifier.
Changed
SystemOnTptp.query_selected_systems/3andquery_all_systems/2return one{system, outcome}per system given, in the order given, whereoutcomemay now also be{:error, {:unknown_system, name}}or{:error, :no_output}.SystemOnTptp.query/2works unconfigured::default_systemhas the library default"E".{:req, "~> 0.7"}; the Finch pool is passed asfinch: [name: …], which removes the deprecation warning Req 0.7 logged for the bare pool name.
Fixed
SystemOnTptp.query_selected_systems/3lost systems. The form fields were collected in a map, so the systems went out sorted by name rather than in the order given, and SystemOnTPTP stops at the first system it does not know —["E---3.5.1", "Vampire---5.0.1", "Nope---1"]returned E alone. An unknown system is now reported as{:error, {:unknown_system, name}}and not submitted, the form keeps the caller's order, and a system the deployment returned no section for is{:error, :no_output}rather than missing from the list.
[0.7.0] - 2026-10-11
The library now sits on tptp, which owns
the TPTP language — reading, linting, printing, the SZS ontology and the
status-line reader — leaving this library the transport to the provers.
Three things change for a caller: what a backend accepts, what it returns,
and where the diagnostics come from. The direct path is unchanged in cost:
a backend never parses what it sends.
Requires {:tptp, "~> 0.1.3"}, the first release whose SZS reader accepts
the framing systems print in practice (#, no for <problem>, StarExec's
elapsed-time prefix) and whose Tptp.Span.offset/3 positions TPTP4X's
findings.
Upgrading
{:ok, :theorem}→{:ok, %AtpClient.Result{status: :theorem}}; match{:ok, %{status: status}}or readresult.status.raw: true→ readresult.output.AtpClient.ResultNormalization.interpret_result(output)→AtpClient.Verdict.interpret(output, backend).AtpClient.Lint.analyze(source)→Tptp.analyze(source)for the local pass,Tptp.Analyzer.run_all(analysis, [AtpClient.Analyzer.Tptp4x])for the remote one; positions are byte spans, rendered withTptp.Analysis.line_column/2.AtpClient.Isabelle.query_tptp(problem)→AtpClient.Isabelle.query(problem); the per-lemma list isresult.details.lemmas, each%{name, status}.
Added
AtpClient.Problem, the protocol everyquery/2takes. Abinary()is sent as it is,iodata()as given (a tree printed withTptp.Printer.Canonical.to_iodata/1), aTptp.Fileas the source it was read from, and aTptp.Unitas its statements in textual-inclusion order, each a sub-binary of the file it came from. None of these copies the problem text. Another library can implement the protocol for its own representation without this one depending on it.AtpClient.Result, what every backend returns on{:ok, _}: the SZSstatusas aTptp.Szs.Ontologyvalue, theevidenceit was read from (:szs,{:pattern, text}or{:backend, module}), the system'soutputverbatim, the delimited SZS outputblocks, theproblemandcommentfrom the status line, thebackendandsystem, and backend-specificdetails.Result.proof/1reads the first proof-family block as TPTP;Result.success?/1follows the ontology.AtpClient.Verdict, the reader behind every result: the SZS status line throughTptp.Szs.status/1, then SystemOnTPTP's<system> says <Value>summary, then the prover-specific patterns for systems that print no SZS line. The pattern table ispatterns/0.AtpClient.query/3andAtpClient.preflight/1.preflight/1reads and lints a problem withTptp.analyze/2and rejects it on any error diagnostic;query/3runs it before a backend when passedpreflight: true. No backend does this on its own.AtpClient.Analyzer.Tptp4x, TPTP4X on the SystemOnTPTP deployment as aTptp.Analyzer, so an editor runs it alongsideTptp.LintthroughTptp.Analyzer.run_all/3. Findings areTptp.Diagnostics with byte spans on the analysed source, positioned byTptp.Span.offset/3.analyze/2returns[]when the deployment cannot be reached;check/2returns the reason.AtpClient.LocalExec.query_file/2hands a path to the prover as it is: nothing is read or copied, and the prover resolves the file'sincludedirectives itself.AtpClient.Isabelle.Verdict, the classifier over Isabelle's messages, previously part ofResultNormalization.
Changed
- Every
query/2returns{:ok, %AtpClient.Result{}}in place of{:ok, szs_status}. Match{:ok, %{status: :theorem}}where{:ok, :theorem}was matched before. The{:error, reason}channel is unchanged in meaning: no status could be obtained. - Backends accept
AtpClient.Problem.t()in place ofString.t();SystemOnTptp.query_system/3,query_selected_systems/3,query_all_systems/2,StarExec.prove/3,StarExec.upload_benchmark/4,Isabelle.prove_tptp/3andLocalExec.query/2take one. - A status word outside the SZS ontology is
{:error, {:unknown_szs_status, word}}. Previously it was converted to an atom withString.to_atom/1, creating atoms from prover output. The ontology's 112 values cover every status a system can legitimately print. - The last status line in an output is the system's answer, as
Tptp.Szs.status/1reads it; previously the first match won. A system refining its answer prints the refinement afterwards. AtpClient.LocalExec's wall-clock kill appendsTptp.Szs.status_line(:timeout, nil, "wall-clock kill by AtpClient.LocalExec")to whatever the prover printed, so the result'soutputkeeps the partial output and itscommentsays what happened. Previously the output was replaced by a line of the backend's own.AtpClient.Isabellereturns aResultfrom every proving call.prove_theory/4carries theuse_theoriespayload indetails.payload;prove_lemmas/4,query_lemmas/3,prove_tptp/3andquery/2carry[%{name: name, status: status}]indetails.lemmasand fold them intostatusweakest-link, asquery/2did before.query/2now takes a problem and does whatquery_tptp/2did.SystemOnTptp.query_selected_systems/3andquery_all_systems/2return{system_id, {:ok, %Result{}} | {:error, failure}}pairs.LocalExecis documented as invoking the prover viaPort.open/2, which is what it has done since 0.3; theSystem.cmd/3wording was stale.
Removed
AtpClient.ResultNormalization.interpret_result/1isAtpClient.Verdict.interpret/3;interpret_isabelle_result/1,per_lemma_results/3andextract_isabelle_text/1areAtpClient.Isabelle.Verdict.classify/1,per_lemma/3andtext/1.AtpClient.Lint,AtpClient.Lint.Local,AtpClient.Lint.Tptp4x,AtpClient.Lint.Diagnostic,AtpClient.Lint.SymbolandAtpClient.Lint.Report. The local pass isTptp.analyze/2, which returns diagnostics, the symbol table and the dialect from one traversal; the remote pass isAtpClient.Analyzer.Tptp4x. The rule that skipped the remote pass when the local pass found an error is editor policy and lives with the editor:Tptp.Diagnostic.any_errors?(analysis.diagnostics).- The
:rawoption on every backend. The output is on every result asoutput. AtpClient.Isabelle.query_tptp/2(nowquery/2) and the deprecated three-arityAtpClient.Isabelle.query/3with its double-wrapped result.
[0.6.2] - 2026-07-20
Point release that drops the compile-time TPTP.thy embed in favour of
IsabelleClient.TPTP.source_text/0, which shipped in :isabelle_elixir
0.4.1 and gives escript-packaged callers the same bytes without a
per-downstream workaround. No API changes.
Changed
:isabelle_elixirconstraint bumped to~> 0.4.1. 0.4.1 fixed the escript-sidepriv/read that 0.6.1 papered over locally. The bump is the minimum needed forIsabelleClient.TPTP.source_text/0to exist.AtpClient.Isabelle.ensure_tptp_theory/1now callsIsabelleClient.TPTP.source_text/0instead of carrying theTPTP.thybytes in an@external_resourcemodule attribute. The upstream module reads its own bundled theory at compile time and exposes the string, so the bytes are still safe to read from an escript — atp_client no longer needs to duplicate the embed. Recompile behaviour onTPTP.thychanges now follows:isabelle_elixir's build, not this library's.
Point release focused on making 0.6 escript-ready, adding editor-grade
option validation, and exposing the per-system flag overrides that
SystemOnTPTPFormReply has always accepted. Nothing about the backend
semantics, SZS ontology, or documented result shapes changes; the pieces
that could affect an existing caller are called out under Changed.
Added
Per-system flag overrides on
AtpClient.SystemOnTptp.query_system/3,query_selected_systems/3, and (transitively)query_all_systems/2accept three new options that are emitted as the correspondingCommand___<sysid>,Format___<sysid>, andTransform___<sysid>form fields on the SystemOnTPTPFormReply POST::command— override the per-system command line (e.g."run_E %s %d THM"). Note: the publictptp.orgdeployment currently ignores this field and runs the default wrapper; the option is still transmitted for self-hosted SystemOnTPTP instances that honour it. Verified againsthttps://tptp.org/cgi-bin/SystemOnTPTPFormReplyat release time and locked in bytest/atp_client/system_on_tptp_live_test.exs.:format— override the input-format module (e.g."tptp:raw").:transform— override the input-transformation stage (e.g."none").
All three default to unset; when omitted, the SystemOnTPTP deployment picks its per-system default and existing behaviour is unchanged.
Live SystemOnTPTP smoke tests.
test/atp_client/system_on_tptp_live_test.exs(tagged:sotptp_live, excluded by default) pins the observed behaviour of the new overrides againsttptp.org. Run withmix test --include sotptp_livewhen the wire contract needs re-verification.
Fixed
Escript packaging of the bundled
TPTP.thy.AtpClient.Isabellenow readsdeps/isabelle_elixir/priv/isabelle/tptp/TPTP.thyat atp_client compile time into a module attribute and writes those bytes from the beam. Mix escripts bundle onlyebin/(the escript is a single zip file), so any runtime read againstApplication.app_dir(:isabelle_elixir, "priv/...")used to resolve through the escript file and fail with:enotdir— visible as{:error, {:tptp_thy_copy_failed, :enotdir}}from everyprove_tptp/3/query_tptp/2call in escript-packaged downstreams (notablyatp_mcp). The compile-time embed removes the runtime filesystem dependency and requires no changes toisabelle_elixir.@external_resourcekeeps recompilation honest when the bundled.thychanges.TPTP axioms are now passed to the injected proof method.
AtpClient.Isabelle.inject_proof_method/2prepends ausing <axiom_names>clause when the isabellized theory containsaxiomatization where …entries, sosledgehammer/by auto/by metis/ … actually see the axioms of the input problem. Without this, TPTP problems whose conjecture depends on named axioms (i.e. almost all of them) surfaced as:gave_up.:isabelle_elixir-independent TPTP theory loading. As a consequence of the two fixes above,AtpClient.Isabelleno longer needsisabelle_elixirto release the pendingsource_text/0patch — the escript path works today against stock 0.4.0.
Changed
Unknown option keys are now rejected at the entry point. Every public function that takes a keyword-list
optsargument (query/2,query_system/3,query_selected_systems/3,query_all_systems/2,verify/1on all four backends,open_session/1,prove_theory/4,prove_lemmas/4,query_lemmas/3,prove_tptp/3,query_tptp/2on Isabelle,login/1,logout/2,get_job/3,get_job_output/3,create_job/3,upload_benchmark/4,list_space_benchmarks/3,wait_for_benchmark/4,prove/3,wait_for_job/3,delete_job/3on StarExec, plusAtpClient.Lint.analyze/2/check/2andAtpClient.Lint.Tptp4x.check/2) now validatesoptsagainst aNimbleOptionsschema before doing any work.Practically: a call that used to silently ignore an unknown key — e.g.
SystemOnTptp.query(problem, time_limit: 5)(missing_secsuffix) — now raisesNimbleOptions.ValidationError(anArgumentErrorsubclass) at the entry point. Documented option spellings and types are unchanged; only unknown keys and obviously-wrong value shapes are rejected. Two escape hatches remain:AtpClient.StarExec.request/4still passes any Req option through verbatim (documented escape hatch), andAtpClient.LocalExeconly validates the shape of:args(list-of-strings) — the flag contents are prover-specific and stay opaque.If you get a
NimbleOptions.ValidationErrorafter upgrading, check the option name against the current docstring; the schemas mirror the documented## Optionssections.nimble_options ~> 1.1is now a direct dependency. It was already fetched transitively via Finch; it is now declared on the atp_client side too so the schemas above compile independently of the transitive graph.
Docs
AtpClient.Isabelle.query/2— THF-only caveat. The docstring now includes an admonition that the bundledisabelle_elixirrelease only processes THF cleanly through the isabellizer; FOF and CNF inputs are best routed through a different backend (SystemOnTptp/LocalExec/StarExec) until the isabellizer supports them.AtpClient.Isabelle.prove_tptp/3—:rawoption documented. The Options section now explains that:rawis accepted for API symmetry with the other query-family functions but is ignored here: the per-lemma path returns a structured[lemma_result()], not a payload map, and there is no useful raw body to surface.Private
aggregate_lemma_results/1carries an inline comment explaining the empty-list →{:ok, :gave_up}collapse and the same THF-only caveat.
Migration notes
For most callers 0.6.1 is a drop-in upgrade. Action is only needed if:
- A previously-working call now raises
NimbleOptions.ValidationError. You are passing an unknown key (usually a typo). Check the current docstring's## Optionssection for the correct spelling; the schema mirrors it exactly. - You want to send prover flags to SystemOnTPTP. Pass any of
:command,:format,:transformonquery_system/3/query_selected_systems/3. Note the:commandcaveat above for the publictptp.orgdeployment.
[0.6.0] - 2026-07-15
Release focused on API polish and getting the surface area 1.0-ready. No behavioural changes to the classifier or backend semantics — the SZS ontology, cancellation contracts, and configuration layering are all unchanged.
Changed
AtpClient.Isabelle.prove_theory/4returns the single-wrappedatp_result()shape. Non-raw mode used to double-wrap the classifier's verdict —{:ok, {:ok, :theorem}}— which contradicted the uniformatp_result()promise every other backend already met. Success now reads{:ok, :theorem}; failures still surface as{:error, term()}. Raw mode is unchanged ({:ok, payload_map}). See migration notes.AtpClient.ResultNormalization.interpret_isabelle_result/1return typespec tightened fromatp_result()to{:ok, szs_status()}. The function never emitted{:error, _}; the outer failure branch was unreachable and forced callers to write dead code. Runtime behaviour is unchanged.AtpClient.StarExec.create_job/3returns{:ok, job_id()}instead of leakingReq.Response.t(). The 302 Location header is parsed for you and diagnostic info is preserved through{:error, {:create_job_failed, %{status, message}}}(withmessagecarrying the StarExecSTATUS_MESSAGE_STRINGcookie when set) and{:error, {:no_job_id, location}}for the malformed-redirect case. Callers wanting the raw response can userequest/4directly.AtpClient.StarExec.upload_benchmark/4returns{:ok, name}instead of{:ok, %{name, status_id: nil | pos_integer}}. Nobody used:status_id, and its nullability forced callers to nil-check on every path.wait_for_benchmark/4takes the name directly.AtpClient.StarExec.request/4opts routing switched from a denylist to an allowlist. Options passed through toReq.request/1are now drawn from an explicit@req_pass_through_optsallowlist derived fromReq.Steps.attach/1. Adding a new StarExec-consumed option (e.g. a new*_path) no longer requires a companion edit to a deny list to prevent Req from rejecting it. No user-visible change for callers that pass documented options.AtpClient.Isabelle.Session.t/0is now@opaque— dialyzer-enforced. Struct fields (:client,:owner,:config) are internal wiring and not part of the public contract; their names and shapes may change across minor releases. Construct only viaopen_session/1, tear down viaclose_session/1. External callers that need the wrapped pids (typically forProcess.monitor/1in a cancellation test) go through the newly exposed accessors onAtpClient.Isabelle.Session:Session.client/1,Session.owner/1,Session.config/1.Under the hood: opacity is achieved by routing every construction through
Session.new/3and every field read through the accessors, soAtpClient.Isabelle(which lives outside the struct's defining module) no longer touches struct internals directly. Dialyzer now catches external%Session{...}construction handed to aSession.t()-typed function (call_without_opaque) and body-level destructuring of an opaque return (%Session{client: _} = open_session(...)→opaque_match).
Deprecated
AtpClient.Isabelle.query/3— kept working with the historical double-wrapped shape ({:ok, {:ok, :theorem}}) so callers that depend on it (in particular the code samples in the AtpClient paper) do not break. New code should useprove_theory/4with an open session,query_lemmas/3/query_tptp/2for single-shot theory/TPTP calls, orquery/2(theAtpClient.Backendentry point).
Removed
AtpClient.LocalExec.resolve_binary/1. Was 100% overlap withverify/1—verifycalls the same resolver internally. Callers that need the resolved path can useSystem.find_executable/1directly.AtpClient.Isabelle.lemma_specs/1demoted to@doc false. Still callable from tests, but no longer part of the documented API surface; it's an implementation detail ofprove_lemmas/4/prove_tptp/3.
Added
AtpClient.Isabelle— zero-config path documented. Whenisabelleis on$PATH(or reachable viaISABELLE_TOOL), the bundled:isabelle_elixirpackage'sIsabelleClient.start_server/1spins up a local server on demand — no manual server management needed. Reflected in theAtpClient.Isabellemoduledoc,README.md, andexamples/demo.livemd.AtpClient.Backendmoduledoc — "Two error channels" section. Explains the distinction between the classifier's{:error, failure}branch (fromatp_result()) and transport / session errors (bare{:error, term()}), with a matching idiom.- Uniform
:rawdocumentation across backends. Eachquery-family function now documents the exact shape returned whenraw: true({:ok, stdout_string},{:ok, body_string}, or{:ok, payload_map}depending on the backend). - Docs / examples audit.
@docand@speccoverage now spans every public function;Config.fetch/4,Config.fetch!/3, andLint.Local.analyze/1gained usable examples.examples/demo.livemdrewritten to cover all four backends including LocalExec (previously missing) and to use the zero-config Isabelle path; the redundantexamples/isabelle_tptp.livemdwas removed.
Migration notes
For most callers the upgrade is a no-op — the deprecated Isabelle.query/3
still works, and only the following patterns need to change:
If you match
{:ok, {:ok, status}}againstIsabelle.prove_theory/4results, drop one level of:ok:# Before {:ok, {:ok, :theorem}} = Isabelle.prove_theory(session, body, "T") # After {:ok, :theorem} = Isabelle.prove_theory(session, body, "T")Match on
{:error, _}for both isabelle-side failures (e.g.{:isabelle_failed, _, _}) and connection errors as before.If you match
{:ok, %Req.Response{} = resp} = StarExec.create_job(...)and pull the job id out ofresp.headersyourself, switch to matching{:ok, job_id} = StarExec.create_job(...). For rich failure diagnostics, match{:error, {:create_job_failed, %{status: s, message: m}}}.If you match
{:ok, %{name: name, status_id: _}} = StarExec.upload_benchmark(...), switch to{:ok, name} = StarExec.upload_benchmark(...).If you call
AtpClient.LocalExec.resolve_binary/1, switch toverify/1(returns:ok | {:error, ...}— drop the resolved path). If you need the resolved path, callSystem.find_executable/1directly.If you call
AtpClient.Isabelle.query/3, either keep the call and accept the deprecation warning, or migrate toprove_theory/4with an open session and unwrap one level of:okfrom the return value.
[0.5.0] - 2026-06-30
Changed
AtpClient.ResultNormalization.atp_result()now surfaces the full SZS Ontology.{:ok, status}carries the SZS verdict as a downcased atom rather than one of six collapsed atoms that lost information. The recognised vocabulary covers every Success and NoSuccess atom a sledgehammer-targeted prover (or a contemporary TPTP-compliant deployment) is likely to emit:Success: :theorem, :unsatisfiable, :satisfiable, :counter_satisfiable, :contradictory_axioms, :equivalent, :counter_equivalent, :counter_theorem, :equivalent_counter_theorem, :equi_satisfiable, :tautology, :tautologous_conclusion, :weaker_conclusion, :no_consequence NoSuccess: :gave_up, :unknown, :incomplete, :timeout, :resource_out, :memory_out, :forced, :user, :inappropriate, :error, :input_errorThe most consequential distinctions the previous shape collapsed are
:theorem("conjecture follows from the axioms", e.g. Isabelle and model-checker endorsement) vs:unsatisfiable("the negated-conjecture clause set has no model", e.g. TPTP refutation provers), and:satisfiable("a model of the premises was found") vs:counter_satisfiable("a model of the negated conjecture was found"). See https://tptp.org/Seminars/SZSOntologies/Summary.html for the full ontology.Prover-specific patterns are reclassified to honest SZS atoms rather than sledgehammer's negated-conjecture reading. SPASS "Completion found" becomes
:satisfiable(was:gave_up); iProver "CNFRefutation" becomes:unsatisfiable(was:theorem); Vampire "Satisfiability detected" and "Termination reason: Satisfiable" become:satisfiable(were:csat); Alt-Ergo "Unknown" becomes:unknown(was:gave_up); Vampire SIGINT becomes:forced(was:interrupted). Callers running a negated-conjecture refutation pipeline should treat:satisfiablefrom these provers as a counter-model to the original goal.SPASS / Waldmeister input-rejection patterns move from
{:error, :malformed_input}to{:ok, :input_error}. SPASS "Undefined symbol", "Free Variable", "No formulae and clauses found in input file", and Waldmeister "Unexpected end of file" are the prover's own SZS InputError verdict — the prover ran, read the input, and rejected it. SPASS "Please report this error" and Waldmeister "Unrecoverable Segmentation Fault" remain{:error, :internal_error}because those are prover crashes, not verdicts. The SZSInappropriatestatus likewise moved from{:error, :malformed_input}to{:ok, :inappropriate}.reqbumped to~> 0.6. ResolvesGHSA-655f-mp8p-96gv(decompression-bomb DoS via auto-decoded archive / compressed response bodies, HIGH) andGHSA-px9f-whj3-246m(multipart form-data header injection, LOW). No API changes at the AtpClient surface.
Added
t:szs_status/0,t:szs_success/0,t:szs_no_success/0typespecs onAtpClient.ResultNormalizationenumerate the SZS atoms recognised explicitly.szs_statuswidens toatom()to admit the permissive fallback below.- Permissive SZS fallback. Any unrecognised
% SZS status <CamelCase>(or… says <CamelCase>) line passes through as its snake_case atom — so SZS additions likeEquivalentTheorembecome{:ok, :equivalent_theorem}without a code change. Bounded length and strict CamelCase keep the atom table safe against pathological input. - Word-boundary SZS line extraction. Replaces substring matching, so
longer names no longer collapse onto shorter prefixes:
EquivalentTheorem≠Equivalent,CounterSatisfiable≠Satisfiable,InputError≠Error.
Breaking
- The collapsed
t:success_t/0atoms (:thm,:csat,:sat,:out_of_resources,:interrupted) are gone fromAtpClient.ResultNormalization.atp_result()and fromAtpClient.Isabelle(which produced:thm/:csat/:sat/:timeout/:out_of_resources/:gave_upfrom the Isabelle message scanner). Pattern matches must be rewritten — see the migration notes. - The
{:error, :malformed_input}failure path is gone. SPASS / Waldmeister input-rejection patterns and the SZSInappropriatestatus surface in the{:ok, _}channel as:input_error/:inappropriaterespectively.failure_t()no longer lists:malformed_input.
Migration notes
A given old atom can map to one of several SZS atoms depending on which prover family produced the result. Match the disjunction to preserve coverage:
:thm→:theorem(Isabelle / Sledgehammer / Alt-Ergo Valid) or:unsatisfiable(TPTP refutation provers, iProver CNFRefutation).:csat→:counter_satisfiable(SZS CounterSatisfiable, Isabelle / Nitpick counter-example) or:satisfiable(SZS Satisfiable, Isabelle / Nitpick model, Vampire "Satisfiability detected", SPASS "Completion found").:sat→:satisfiable(only emitted by the Isabelle classifier for Nitpick / Quickcheck models).:out_of_resources→:resource_out(SZS ResourceOut, SPASS / Waldmeister resource patterns) or:memory_out(SZS MemoryOut, Isabelle "Out of memory").:interrupted→:forced(SZS Forced, Vampire SIGINT) or:user(SZS User).:gave_upagainst an Alt-Ergo verdict →:unknown; against SZSIncomplete→:incomplete; otherwise still:gave_up.{:error, :malformed_input}→ match{:ok, :input_error}instead.
So a previously written TPTP triage like
case AtpClient.SystemOnTptp.query(problem, default_system: "cvc5---1.3.0") do
{:ok, :thm} -> :proved
{:ok, :csat} -> :disproved
{:ok, atom} when atom in [:timeout, :out_of_resources, :gave_up, :interrupted]
-> :inconclusive
{:error, :malformed_input} -> :bad_input
{:error, _} -> :error
endbecomes
case AtpClient.SystemOnTptp.query(problem, default_system: "cvc5---1.3.0") do
{:ok, status} when status in [:theorem, :unsatisfiable]
-> :proved
{:ok, status} when status in [:satisfiable, :counter_satisfiable]
-> :disproved
{:ok, status} when status in [:timeout, :resource_out, :memory_out,
:gave_up, :unknown, :incomplete,
:forced, :user, :inappropriate]
-> :inconclusive
{:ok, :input_error} -> :bad_input
{:error, _} -> :error
end[0.4.0] - 2026-06-29
Changed
AtpClient.ResultNormalization.per_lemma_results/2becomes/3and now takes(payload, lemma_specs, opts)instead of(payload, opts). The caller supplies one%{name: String.t(), range: Range.t()}spec per lemma (AtpClient.Isabelle.lemma_specs/1builds these from the theory body) and the classifier buckets messages by body-line range rather than re-deriving structure frompos.linealone. Returnedlemma_result()maps lose the:linefield — line numbers referred to the generated theory file, not the caller's source, and were ambiguous when a bucket contained multiple messages.- Per-lemma classifier no longer scans concatenated text across
messages. Each message is classified individually before the bucket is
reconciled. This fixes two regressions visible with multi-lemma Isabelle
jobs: (a)
Nitpick found a model+Nitpick found no counterexamplesurfaced as:csatbecause the"Nitpick found a" + "counterexample"substring test fired across the message join, and (b)by <tactic>failing on a False goal was reported as:thmbecause Isabelle echoestheorem name: <goal>at thebyposition alongside theFailed to finish prooferror. Verdict precedence is documented onper_lemma_results/3. per_lemma_results/3accepts:fileto drop messages whosepos.filedoes not end with a given suffix.prove_lemmas/4uses this to filter out noise from the bundledTPTP.thyand other transitively imported theories that previously produced phantom lemma rows.- Lemma names always come through on the per-lemma path — the name
comes from the body (via
lemma_specs/1), not from parsingtheorem name:out of Isabelle messages. Sledgehammer / Nitpick verdicts no longer surface asname: nil.
Added
AtpClient.Isabelle.lemma_specs/1— extracts[%{name, range}]from a theory body. Used internally byprove_lemmas/4andprove_tptp/3; exposed for callers that want to pre-compute specs.
Migration notes
- If you call
ResultNormalization.per_lemma_results(payload)orper_lemma_results(payload, line_offset: n), switch toper_lemma_results(payload, specs, opts)wherespecscome fromAtpClient.Isabelle.lemma_specs(body). The high-levelprove_lemmas/4/prove_tptp/3/query_tptp/2entry points already do this for you. - Drop reads of the
:linefield onlemma_resultentries. Attribute by:nameinstead — line numbers were a generated-theory leak and have been removed from the returned shape.
[0.3.0] - 2026-06-26
Added
AtpClient.LocalExecbackend. Invokes a locally installed, TPTP-compliant prover binary (E, Vampire, …) viaSystem.cmd/3and normalizes its stdout through the existing SZS classifier. Two-layered timeout: the prover-side CPU limit (passed via:args) lets provers emit a cleanSZS status Timeout, and an independent BEAM-side wall-clock timeout (:wall_timeout_ms) kills wedged processes. Both paths fold into the same{:ok, :timeout}result so callers do not have to branch on the failure mode. Binary resolution goes throughSystem.find_executable/1; missing binaries surface{:error, {:prover_not_found, name}}rather than raising.AtpClient.Backendbehaviour so a UI (Smart Cell, Livebook, …) can enumerate and drive backends without hard-coding per-backend knowledge. Every backend (SystemOnTptp,StarExec,Isabelle,LocalExec) now implementsconfig_key/0,label/0,config_schema/0,verify/1, andquery/2. The newquery/2is the cross-backend entry point: it takes a TPTP-format problem string plus a keyword list and returns a singleatp_result(), hiding session login/logout, prover selection, theory bookkeeping, etc. Per-backend low-level entry points (query_system/3,prove_theory/4,create_job/3,query_tptp/2, …) remain available for callers that need sessions, multi-system fan-out, or per-lemma detail.AtpClient.Config.Fieldstruct carrying the UI metadata each backend exposes viaconfig_schema/0: logical:type(:string | :integer | :boolean | :string_list), layout:group(:connection | :defaults | :advanced),:required?,:secret?,:default,:label, and:doc. The library performs no coercion; values flow intoApplication.put_env/3and the per-call opts as-is.AtpClient.backends/0returns the list of behaviour-implementing backend modules so a UI can discover them without hard-coding.- TPTP-shaped entry points on
AtpClient.Isabelle.query_tptp/2andprove_tptp/3accept a TPTP/THF problem string, route it throughIsabelleClient.TPTP.isabellize_theory/1, append the configured:proof_method(default"by auto") to each generatedlemma, and submit the resulting theory to Isabelle withimports "TPTP"andunbundle from_TPTPactive. The bundledTPTP.thysupport theory is copied into:local_diron first use so Isabelle's loader can resolve the import. Lemma names carry over from TPTP formula names. Passproof_method: "by metis","sledgehammer", etc. for stronger or probing tactics. Smart-cell consumers can now drive Isabelle from the same TPTP editor they use for the SystemOnTPTP / StarExec / LocalExec backends; theory-text entry points (prove_theory/4,prove_lemmas/4,query/3,query_lemmas/3) remain unchanged. AtpClient.Isabelle.SessionOwner— a private GenServer that owns the link toIsabelleClient.Shared.open_session/1now returns{:error, reason}on a failed connection without killing non-trapping callers, a later Shared crash surfaces through a monitor rather than an:EXIT, and dropping the caller monitors the owner to a clean shutdown so server-side Isabelle sessions are no longer orphaned. TheSessionstruct gains an opaque:ownerfield; treat it as internal.scripts/build_eprover.sh— builds the E theorem prover from source and installs it topriv/bin/eproverfor use as the:local_execbackend's binary.AtpClient.ResultNormalization.failure_t/0gains{:prover_not_found, String.t()}for theLocalExecbinary-resolution failure mode.
Changed
- Library defaults are merged per-key inside
AtpClient.Config.get/2instead of being seeded viamix.exs's:envblock. The previous arrangement let anyconfig :atp_client, :<backend>, …in userconfig.exssilently replace the whole keyword list (the OTPApplication.put_env/3semantics), dropping defaults the user had not explicitly re-set — most visibly causing a fresh install with partial SystemOnTPTP config to fail with{:unrecognized_output, ""}because:urlhad vanished. Defaults now live inAtpClient.Config's@defaults(exposed viaConfig.defaults/0) and are merged underneath Application env on every read, so a partial user config only overrides the keys it actually names. No call-site changes required. AtpClient.SystemOnTptp.list_provers/0blocks on the first call until the startup refresh completes or:sotptp, :refresh_timeout_ms(default 15 s) elapses; subsequent calls return the cached list immediately. On timeout it returns[]rather than raising. Previously it could return[]immediately after application start, which the caller had no way to distinguish from "the SystemOnTPTP deployment is empty."per_lemma_results/2now appliescheck_tool_signalsper source line. Sledgehammer's"found a proof"and Nitpick's"found a counterexample"/"found a model"verdicts surface as{:ok, :thm}/{:ok, :csat}/{:ok, :sat}on the per-lemma path, matchinginterpret_isabelle_result/1. Previously only Isabelle's"theorem name:"completion notification was recognised, so probe-style proofs (sledgehammer nitpick oops) collapsed to:gave_up.{:ok, :timeout}and{:ok, :out_of_resources}are now surfaced per lemma too. Lemma names attach to results derived fromtheorem name:completions; sledgehammer/nitpick verdicts carryname: nilbecause the underlying messages do not name the lemma.:isabelle_elixiris now pinned to its GitHubmainbranch (previously~> 0.3from Hex). The new TPTP entry points rely on theIsabelleClient.TPTPmodule, which is not in the 0.3.0 Hex release. Re-pin to a Hex constraint once a release containingIsabelleClient.TPTPships.
Breaking
AtpClient.Isabelle.query/3no longer defaults itsoptsargument. Pass[]explicitly if you have no options to set. The default was removed so the three-arityquery/3(theory text + name) does not collide with theBackendbehaviour'squery/2(TPTP problem string + opts), both of which now live on the same module.AtpClient.Isabelle.Sessiongained an enforced:ownerfield. Anyone constructing%Session{client: …, config: …}outside the library must now also pass:owner(the pid of anAtpClient.Isabelle.SessionOwner). TreatSessionas opaque and construct it only viaopen_session/1.
Migration notes
- If you call
Isabelle.query(theory, name), change it toIsabelle.query(theory, name, []). - If you read the
:envblock ofAtpClient.MixProjectto discover library defaults, callAtpClient.Config.defaults/0instead. The:envblock has been removed since the defaults are no longer load-bearing there. - If you construct
%AtpClient.Isabelle.Session{}directly, stop — useopen_session/1so the:ownerpid is set up correctly.
[0.2.0]
Changed
- Bumped
:isabelle_elixirto~> 0.2. The upstream library has moved to a task-based wire model;AtpClient.Isabellehas been rewritten on top of the newIsabelleClient(the stateful struct client) instead ofIsabelleClientMini's polling API. End users of the high-level functions (open_session/1,prove_theory/4,query/3,close_session/1) see the same surface — only the internals and a few error / raw-result shapes have changed. AtpClient.Isabelle.Sessionnow wraps an%IsabelleClient{}rather than carrying:socketand:session_iddirectly. Code that pattern-matched%Session{socket: socket, session_id: id}must instead use%Session{client: %IsabelleClient{socket: socket, session_id: id}}.:rawmode ofprove_theory/4andquery/3now returns theuse_theoriesresult map directly (with top-level"ok","errors","nodes"keys) instead of the previous keyword list of poll messages.AtpClient.ResultNormalization.extract_isabelle_text/1already accepted this map shape, so call sites that used it continue to work unchanged.AtpClient.ResultNormalization.interpret_isabelle_status/1has been renamed tointerpret_isabelle_result/1and now takes theuse_theoriesresult map directly. The keyword-list input form is no longer supported — the previous name and shape were leaks of the old polling abstraction.AtpClient.ResultNormalization.extract_isabelle_text/1no longer accepts a keyword list; only the result map is supported.{:error, {:isabelle_failed, payload, status}}errors now carrypayload(the FAILED body) andnotes(intermediateNOTEpayloads accumulated byIsabelleClient.Task) instead of the old full poll-message keyword list. The "Cannot load theory file" annotation path continues to emit a 3-tuple{:isabelle_failed, payload, [hint:, local_dir:, isabelle_dir:]}as before.
Removed
- The
:poll_interval_msIsabelle config setting is no longer used — the new client uses blockingawait_tasksemantics rather than polling. Existingconfig :atp_client, :isabelle, poll_interval_ms: ...entries are removed.
Migration notes
For most callers the upgrade is a no-op beyond updating mix.exs. Action is
only needed if you:
- Pattern-match on
AtpClient.Isabelle.Sessionfields directly. Usesession.client.socket/session.client.session_idinstead, or treatSessionas opaque. - Call
AtpClient.ResultNormalization.interpret_isabelle_status/1. Rename tointerpret_isabelle_result/1and pass theuse_theoriespayload map (ortask.resultof a finishedIsabelleClient.Task) instead of the previous poll keyword list. - Use
prove_theory(..., raw: true)orquery(..., raw: true)and consume the result with anything other thanextract_isabelle_text/1. The shape is now a plain map; expectpayload["nodes"],payload["ok"], etc. - Match on the third element of
{:error, {:isabelle_failed, _, _}}errors. It is now either a list of NOTE payload maps (plain failures) or a[hint:, local_dir:, isabelle_dir:]keyword list (annotated "Cannot load theory file" failures).
[0.1.3]
Prior versions are not retroactively documented here. See git history for changes before 0.2.0.