Isabelle_RPC: Call Python from Isabelle/ML
Isabelle_RPC lets Isabelle/ML call Python functions via RPC. Communication flows Isabelle → Python (with callbacks). For Python → Isabelle, use Isa-REPL instead.
Quick Start
- Start the Python RPC server:
python Isabelle_RPC/launcher.py
- In your theory file:
imports Remote_Procedure_Calling
- Call Python procedures from ML code
Writing RPC Procedures (Python Side)
Basic Pattern
Register Python functions that Isabelle can call:
from Isabelle_RPC_Host import isabelle_remote_procedure, Connection
@isabelle_remote_procedure("my_function") # the registered procedure name
def my_function(arg, connection: Connection):
# arg: data sent from Isabelle
# connection: can be used for callbacks or logging
result = ... # Your process
return result # returned to Isabelle
Examples
@isabelle_remote_procedure("heartbeat")
def _heartbeat_(arg, connection: Connection) -> None:
connection.server.logger.info(f"Heartbeat from {connection.client_addr}")
return None
@isabelle_remote_procedure("compute_sum")
def _compute_sum(arg, connection: Connection) -> int:
# arg is a list of integers from ML
return sum(arg)
Calling RPC Procedures (Isabelle/ML Side)
Define a Command
Create a typed command specification with MessagePack schemas:
type ('a,'b) command = {
name : string, (* registered procedure name *)
arg_schema : 'a MessagePackBinIO.Pack.packer, (* argument schema *)
ret_schema : 'b MessagePackBinIO.Unpack.unpacker, (* return-value schema *)
callback : (connection -> unit) option, (* optional callback during call *)
timeout : Time.time option (* optional timeout *)
}
Examples:
open MessagePackBinIO.Pack MessagePackBinIO.Unpack
val heartbeat_cmd = {
name = "heartbeat",
arg_schema = packUnit,
ret_schema = unpackUnit,
callback = NONE,
timeout = SOME (Time.fromSeconds 10)
}
val compute_sum_cmd = {
name = "compute_sum", (* Must match @isabelle_remote_procedure("compute_sum") *)
arg_schema = packList packInt, (* Send list of integers *)
ret_schema = unpackInt, (* Receive integer result *)
callback = NONE,
timeout = SOME (Time.fromSeconds 30)
}
Call the Command
(* Using connection pool *)
val result = Remote_Procedure_Calling.call_command compute_sum_cmd [1, 2, 3, 4]
(* With explicit connection *)
val conn = Remote_Procedure_Calling.get_connection ()
val result = Remote_Procedure_Calling.call_command' compute_sum_cmd conn [1, 2, 3, 4]
val _ = Remote_Procedure_Calling.release_connection conn
Using Callbacks
Callbacks allow Python to call back into Isabelle during RPC execution. This enables bidirectional communication within a single RPC call.
Defining Callbacks (Isabelle/ML Side)
Define callbacks that Python can invoke:
open MessagePackBinIO.Pack MessagePackBinIO.Unpack
val my_callback : (string, int) Remote_Procedure_Calling.callback = {
name = "my_callback", (* callback identifier *)
arg_schema = unpackString, (* Python → ML schema *)
ret_schema = packInt, (* ML → Python schema *)
function = (fn msg => String.size msg), (* callback logic *)
timeout = NONE
}
(* Either register globally using Remote_Procedure_Calling.register_global_callback *)
val _ = Theory.setup (Context.theory_map
(Remote_Procedure_Calling.register_global_callback my_callback))
(* Or pass as local callback in command definition *)
val my_cmd : (unit, string) Remote_Procedure_Calling.command = {
name = "my_rpc",
arg_schema = packUnit,
ret_schema = unpackString,
callback = [Remote_Procedure_Calling.mk_callback my_callback], (* local *)
timeout = SOME (Time.fromSeconds 10)
}
Calling Callbacks (Python Side)
Invoke Isabelle callbacks from Python RPC procedures:
@isabelle_remote_procedure("my_rpc")
def my_rpc(arg, connection: Connection):
# Call back to Isabelle's "my_callback"
result = connection.callback("my_callback", "hello")
# result = 5 (length of "hello")
return f"Callback returned: {result}"
Advanced: For custom bidirectional protocols, define ML callbacks using callback' type directly (bypassing structured schemas) and invoke with connection.raw_callback(name, action) where action is a function receiving the connection for arbitrary I/O operations.
The built-in isabelle_heartbeat callback (RPC.ML:328) provides a working example. See contrib/Isabelle_RPC/test_callback.py for complete examples.
Common MessagePack Schemas
Packing (ML → Python)
packUnit- unit/NonepackString- stringpackInt- integerpackBool- booleanpackReal- floatpackList schema- listpackPair (s1, s2)- 2-tuplepackTuple3 (s1, s2, s3)- 3-tuplepackTuple3,packTuple4,packTuple5- up to 8-tuplepackOption schema- option/OptionalpackPairList (s1, s2)- list of pairs (dict items)
Unpacking (Python → ML)
unpackUnit- unit/NoneunpackString- stringunpackInt- integerunpackBool- booleanunpackReal- floatunpackList schema- listunpackPair (s1, s2)- 2-tupleunpackTuple3 (s1, s2, s3)- 3-tupleunpackTuple4,unpackTuple5- up to 6-tupleunpackOption schema- option/OptionalunpackPairList (s1, s2)- list of pairs (dict items)
Schema reference: Performant_Isabelle_ML/contrib/mlmsgpack/mlmsgpack.sml (relocated from Isabelle_RPC; loaded by the Performant_Isabelle_ML base session)
Server Configuration
Since 0.4.0, whoever launches the host owns its lifetime:
RPC_Hostunset (default): per-session ephemeral host. On first use, Isabelle launches a private host on an OS-assigned port as an attached child. It dies with that Isabelle process on every exit path (includingkill -9), guarded by a TCP lifeline and a 300 s startup leak guard. Nothing to configure, nothing to clean up.RPC_Hostset (e.g.export RPC_Host=127.0.0.1:9999): external host, external lifecycle. Isabelle only connects, and errors if nothing is listening — it never launches at a configured address. Start the host yourself with theisabelle-rpc-hostconsole script,python launcher.py, orpython -c 'import Isabelle_RPC_Host; Isabelle_RPC_Host.fork_and_launch__()' <host:port> <log>. Use this mode whenever several Isabelle processes must share one host.- Protocol: MessagePack over TCP.
Removed in 0.4.0 (behavior change): the fixed 127.0.0.1:27182 default address,
auto-launching at a configured RPC_Host address, and the AUTO_START_RPC_SERVER
variable (now ignored; exporting AUTO_START_RPC_SERVER=0 is a harmless no-op).
Workflows that set RPC_Host and relied on auto-launch must pre-launch the host
externally. Old wheels (< 0.4.0) lack the run_attached__ entry point and fail loudly
with an actionable message. Ephemeral host logs land in
$ISABELLE_HOME_USER/log/RPC_attached_<token>.log.
Key Files Reference
Entry points:
Remote_Procedure_Calling.thy- Main theory filelauncher.py- Server startup script
Python server:
Isabelle_RPC_Host/__init__.py- Server implementation and procedure registration
ML client:
Tools/RPC.ML- Client implementation (signatureREMOTE_PROCEDURE_CALLING)
Relationship with Other Projects
- Isa-REPL: Python → Isabelle (opposite direction from RPC)
Release files for isabelle-rpc 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| isabelle_rpc-0.4.0.tar.gz | 55.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| isabelle_rpc-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 118.2 kB
Release files / isabelle_rpc-0.4.0.tar.gz
| Download URL | isabelle_rpc-0.4.0.tar.gz |
|---|---|
| Size | 55.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eb347968831227c8a897f6dcd9aa288b213dcd66acefc899495719ad88b798e4
|
|
BLAKE2b-256 checksum How to use checksums |
c48b78d750e063b56a9d77cf1f9136abdbbcb9fbe2936f9741a89e5ba0617922
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.11
|
Release files / isabelle_rpc-0.4.0-py3-none-any.whl
| Download URL | isabelle_rpc-0.4.0-py3-none-any.whl |
|---|---|
| Size | 63.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7c5746f892dff8a6ca7681df30b2e45d938e5915a6309dbdb127ba4c89941eb7
|
|
BLAKE2b-256 checksum How to use checksums |
4fdb5bf244d46e2412749ded64a40c256258b28f25f6c57cc9b4305c5e9ce24b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.11
|