backends¶
A ComputeBackend runs a command over a working
directory inside a Lean+Mathlib sandbox. It is the single load-bearing primitive of
the platform, used both to run a coding agent and to run lake env lean ... for
verification. A ComputeSession keeps that sandbox
alive across several commands – generation then verification against the same hot
filesystem – without paying a second spin-up.
Base¶
- class open_atp.backends.base.ComputeBackend(*, image: Image | Mapping[str, object] = Image(name='open-atp:latest', lean_toolchain='leanprover/lean4:v4.28.0', mathlib_rev='v4.28.0'), env: Mapping[str, str] | None = None)[source]¶
Runs commands over a workdir in a sandbox carrying Lean + Mathlib.
- Parameters:
- image
Image, defaultDEFAULT_IMAGE The sandbox image carrying Lean + Mathlib – its tag plus the toolchain and Mathlib revision the verifier checks projects against. A mapping is coerced to an
Image(so a parsed config’s nestedimage:block works).- env
Mapping[str,str], optional Environment variables baked into every command run in the sandbox. Defaults to no extra variables.
- image
- abstract property name: str¶
Short identifier for the backend, such as
dockerormodal.Used to label log records and benchmark rows.
- abstract property wallclock_overhead_s: int¶
Wall-clock time budget required beyond a command’s timeout, in seconds.
The
timeout_spassed torun()/session()is the command’s wall-clock budget. The backend may need extra time for spin-up, teardown, file transfer, etc. The time allotted for this overhead is captured here, so a caller can bound the total wall-clock for a run.
- abstractmethod session(workdir: Path, *, timeout_s: int, env: Mapping[str, str] | None = None, mounts: Sequence[tuple[str, str]] | None = None) ComputeSession[source]¶
Open a persistent sandbox over
workdirfor multipleexec()calls.Unlike
run()(one command, then teardown), the returnedComputeSessionstays alive untilComputeSession.close(), so the same hot sandbox can run generation and verification back to back.env/mountshere pin the long-lived sandbox at creation (Docker bind mounts can only be set atdocker run); per-command credentials go toComputeSession.exec()’s ownenv.- Parameters:
- workdir
pathlib.Path Host directory mounted/synced into the sandbox for the session’s life.
- timeout_s
int Wall-clock cap for the sandbox, in seconds.
- env
Mapping[str,str], optional Environment variables pinned on the sandbox at creation, merged over the backend’s
env.- mounts
Sequence[tuple[str,str]], optional Extra
(host_path, container_path)mounts pinned at creation.
- workdir
- Returns:
ComputeSessionA live session over the workdir; close it via
ComputeSession.close()(use as a context manager).
- run(workdir: Path, command: str, *, timeout_s: int, env: Mapping[str, str] | None = None, mounts: Sequence[tuple[str, str]] | None = None) CommandResult[source]¶
Run a single
commandin a fresh sandbox overworkdir, then tear down.A one-shot
session(): the workdir (and anymounts) is synced in, the command runs to completion, and the sandbox is torn down – pulling file mutations (completed proofs) back to the host on the way out.- Parameters:
- workdir
pathlib.Path Host directory mounted/synced into the sandbox; mutations sync back out.
- command
str The shell command to run inside the sandbox.
- timeout_s
int Wall-clock cap for the command, in seconds.
- env
Mapping[str,str], optional Per-call environment variables, merged over the backend’s
env.- mounts
Sequence[tuple[str,str]], optional Extra
(host_path, container_path)bind mounts beyond the workdir.
- workdir
- Returns:
CommandResultExit code, captured stdout/stderr, and wall-clock duration.
- class open_atp.backends.base.CommandHandle[source]¶
A live command running in a session’s sandbox. Backends fill the hooks.
Streaming matters for agents (we want incremental stdout for cost/progress parsing) but is equally usable for a blocking
lakeinvocation viawait(). The handle owns no teardown: the sandbox is the session’s, torn down byComputeSession.close().- stream() Iterator[str][source]¶
Yield stdout lines as they arrive.
- Yields:
strEach line of the command’s standard output, newline-stripped, as produced.
- wait() CommandResult[source]¶
Drain to completion and return the final result.
- Returns:
CommandResultExit code, captured stdout/stderr, and wall-clock duration.
- class open_atp.backends.base.CommandResult(exit_code: int, stdout: str, stderr: str, duration_s: float)[source]¶
Outcome of a finished command run inside a backend.
- class open_atp.backends.base.ComputeSession[source]¶
A persistent sandbox over a workdir, exec’d many times, torn down once.
The one-shot
ComputeBackend.run()creates a sandbox, runs a single command, and tears it down. A session keeps the sandbox alive so several commands can run against the same hot filesystem – the agent’s generation then the verifier’s compile – without paying a second spin-up.Lifecycle invariant: teardown lives in
close()(not in the handle’swait), so a session MUST be used as a context manager andclose()MUST be idempotent – otherwise an error between exec and close leaks the sandbox.Examples
Open a session over a workdir and run several commands against the same hot sandbox – here generation followed by the compile, with one spin-up (needs a live backend, so this is illustrative rather than a doctest):
from open_atp.backends.docker import DockerBackend from open_atp.images import DEFAULT_IMAGE backend = DockerBackend(image=DEFAULT_IMAGE) with backend.session(workdir, timeout_s=1800) as session: result = session.exec("lake env lean Demo.lean", timeout_s=300).wait() session.sync_out() # pull the agent's edits back to the host # the sandbox is torn down on exit, even if exec raised
Docker¶
- class open_atp.backends.docker.DockerBackend(*, image: Image | Mapping[str, object] = Image(name='open-atp:latest', lean_toolchain='leanprover/lean4:v4.28.0', mathlib_rev='v4.28.0'), env: Mapping[str, str] | None = None, volumes: tuple[tuple[str, str], ...] = ())[source]¶
Bases:
ComputeBackendRun sandboxes as local
dockercontainers over a bind-mounted workdir.The workdir is bind-mounted (not copied), so a command’s file edits land directly on the host. Construction is offline – it just records its knobs; the daemon is only contacted when a command runs.
- Parameters:
- image
Image, defaultDEFAULT_IMAGE The sandbox image carrying Lean + Mathlib – its tag plus the toolchain and Mathlib revision the verifier checks projects against. A mapping is coerced to an
Image(so a parsed config’s nestedimage:block works).- env
Mapping[str,str], optional Environment variables baked into every command run in the sandbox. Defaults to no extra variables.
- volumes
tuple[tuple[str,str], …], default () Extra
-v host:containermounts (e.g. agent credential dirs).
- image
Examples
Constructing the backend records its config without contacting the daemon:
>>> from open_atp.backends.docker import DockerBackend >>> from open_atp.images import DEFAULT_IMAGE >>> backend = DockerBackend(image=DEFAULT_IMAGE) >>> backend.name 'docker'
- property wallclock_overhead_s: int¶
Docker container start/teardown have near-zero overhead; use small buffer.
- session(workdir: Path, *, timeout_s: int, env: Mapping[str, str] | None = None, mounts: Sequence[tuple[str, str]] | None = None) ComputeSession[source]¶
Start a detached keep-alive container for repeated
docker execcommands.All mounts are wired at
docker runtime (Docker can’t add them per-exec); the container lives untilDockerSession.close().- Parameters:
- workdir
pathlib.Path Host directory bind-mounted at
WORKDIR_MOUNTfor the session’s life.- timeout_s
int Unused by Docker (the container has no built-in cap).
- env
Mapping[str,str], optional Environment variables pinned on the container at creation, merged over the backend’s
env.- mounts
Sequence[tuple[str,str]], optional Extra
(host_path, container_path)bind mounts wired at creation (Docker can’t add them per-exec).
- workdir
- Returns:
ComputeSessionA live
DockerSessionover the workdir.
- class open_atp.backends.docker.DockerSession(backend: DockerBackend, container: str)[source]¶
Bases:
ComputeSessionA live
docker run -dcontainer; exec many commands,docker killonce.The workdir is bind-mounted, so
sync_out()/sync_in()are no-ops – edits already live on the host.- Parameters:
- backend
DockerBackend The backend that provisioned the container.
- container
str Name of the running container commands are
docker exec’d into.
- backend
- exec(command: str, *, timeout_s: int, env: Mapping[str, str] | None = None) CommandHandle[source]¶
docker execcommandinto the container; close() owns teardown.The command is wrapped in coreutils
timeoutso it is killed (only the exec, not the container) oncetimeout_selapses, leaving the session up for the next command.
Modal¶
- class open_atp.backends.modal.ModalBackend(*, image: Image | Mapping[str, object] = Image(name='open-atp:latest', lean_toolchain='leanprover/lean4:v4.28.0', mathlib_rev='v4.28.0'), env: Mapping[str, str] | None = None, cpu: float = 2.0, memory_mib: int = 4096, app: str = 'open-atp', region: str | Sequence[str] | None = 'us')[source]¶
Bases:
ComputeBackendRun sandboxes as Modal Sandboxes, pushing the workdir up and pulling it back.
Unlike the bind-mounted Docker backend, the workdir is synced into the remote Sandbox and synced back out on completion. Construction is offline – it just records its knobs; Modal is only contacted when a command runs.
- Parameters:
- image
Image, defaultDEFAULT_IMAGE The sandbox image carrying Lean + Mathlib – its tag plus the toolchain and Mathlib revision the verifier checks projects against. A mapping is coerced to an
Image(so a parsed config’s nestedimage:block works).- env
Mapping[str,str], optional Environment variables baked into every command run in the sandbox. Defaults to no extra variables.
- cpu
float, default 2.0 CPU cores requested for the Modal Sandbox.
- memory_mib
int, default 4096 Memory (MiB) requested for the Modal Sandbox.
- app
str, default “open-atp” Modal app the Sandbox is associated with (also the publish target of
open-atp build-modal-image).- region
strorSequence[str], default “us” Region(s) to schedule sandboxes in (see Modal region selection docs).
Nonelets Modal choose freely.
- image
Examples
Constructing the backend records its config without provisioning a Sandbox:
>>> from open_atp.backends.modal import ModalBackend >>> from open_atp.images import DEFAULT_IMAGE >>> backend = ModalBackend(image=DEFAULT_IMAGE, cpu=4.0) >>> backend.name 'modal' >>> backend.cpu 4.0 >>> backend.app 'open-atp' >>> backend.region 'us'
- property wallclock_overhead_s: int¶
Modal Sandbox lifecycle overhead: file sync + warm build + teardown.
- session(workdir: Path, *, timeout_s: int, env: Mapping[str, str] | None = None, mounts: Sequence[tuple[str, str]] | None = None) ComputeSession[source]¶
Provision a Sandbox over
workdirand keep it alive for many execs.The Sandbox lives until
ModalSession.close();env/mountspin it at creation.- Parameters:
- workdir
pathlib.Path Host directory pushed into the Sandbox at creation; bridge it back with
ModalSession.sync_out().- timeout_s
int Wall-clock time for the Modal Sandbox session to live, in seconds.
- env
Mapping[str,str], optional Environment variables pinned on the Sandbox at creation, merged over the backend’s
env.- mounts
Sequence[tuple[str,str]], optional Extra
(host_path, container_path)dirs pushed in at creation.
- workdir
- Returns:
ComputeSessionA live
ModalSessionover the workdir.
- class open_atp.backends.modal.ModalSession(backend: ModalBackend, sb: Sandbox, workdir: Path)[source]¶
Bases:
ComputeSessionA live Modal Sandbox: exec many commands over the pushed workdir, terminate once.
The filesystem is isolated, so
sync_out()(tar pull) andsync_in()(tar push) bridge host<->Sandbox when a caller needs the host workdir current between commands, e.g. to diff the staged files partway through a run.- Parameters:
- backend
ModalBackend The backend that provisioned the Sandbox.
- sb
modal.Sandbox The live Sandbox commands execute in.
- workdir
pathlib.Path Host directory mirrored into the Sandbox and pulled back on sync.
- backend