Source code for milp_flare.harness.runner.base
from __future__ import annotations
import time
from abc import ABC, abstractmethod
from collections.abc import Iterator
from contextlib import AbstractContextManager
from dataclasses import dataclass
from pathlib import Path
from types import TracebackType
from typing import ClassVar
#: The default name of the Docker image containing the agent environment. This
#: image is expected to be built prior to running FLARE. See :doc:`/installation`.
IMAGE = "flare-agent:latest"
[docs]
@dataclass(frozen=True)
class AuthSpec:
"""Compute-agnostic description for forwarding agent credentials.
Attributes
----------
env : list[str]
Host environment-variable names to forward into the container.
home_dirs : list[tuple[pathlib.Path, str]]
Host directories to make available under the container's ``$HOME``, as
``(host_dir, dest_basename)`` pairs (e.g. ``(~/.codex, ".codex")``).
"""
env: list[str]
home_dirs: list[tuple[Path, str]]
[docs]
class AgentRun(AbstractContextManager["AgentRun"]):
"""Handle for a running agent, returned by :meth:`Runner.start`.
The handle owns the lifecycle of an active agent run. The caller is
responsible for iterating :attr:`stdout` to retrieve agent output until
the run ends (if :attr:`stdout` is not drained, the buffer will fill and
block the agent). Use :meth:`close` to stop the agent, capture any partial
artifacts, and release the compute. Additionally, :meth:`cancel` offers a
thread-safe way to stop the agent without waiting for completion.
Attributes
----------
stdout : Iterator[str]
Iterator yielding the agent's ``stdout`` lines until the process exits
(EOF).
duration_s : float
Wall-clock duration of the run, in seconds. ``0.0`` until :meth:`close`
runs and sets it.
Returns
-------
AgentRun
Handle to the in-flight run.
"""
duration_s: float = 0.0
_start: float | None = None
_closed: bool = False
def __init__(self, start: float | None = None) -> None:
if start is not None:
self._start = start
@property
@abstractmethod
def stdout(self) -> Iterator[str]: ...
[docs]
@abstractmethod
def cancel(self) -> None:
"""Stop the agent process.
This does not capture partial artifacts or release compute. It solely
stops the agent process and makes the :attr:`stdout` iteration end.
This allows for thread-safe cancellation. Any consumer thread can detect
cancellation by the end of the stream.
"""
...
def _teardown(self) -> None:
"""Stop the agent, capture partial artifacts, and release the compute."""
self.cancel()
[docs]
def close(self) -> None:
"""Stop the agent, capture partial artifacts, and release the compute."""
if self._closed:
return
self._closed = True
try:
self._teardown()
finally:
if self._start is not None:
self.duration_s = time.time() - self._start
def __exit__(
self,
exc_type: type[BaseException] | None,
exc: BaseException | None,
tb: TracebackType | None,
) -> None:
self.close()
[docs]
class Runner(ABC):
"""Launch the FLARE agent container for a populated working directory.
Attributes
----------
name : str
Compute backend identifier (e.g. ``"docker"``).
home : str
Absolute path of the container ``HOME`` for this backend.
image : str
Image identifier for this runner.
"""
name: ClassVar[str]
home: ClassVar[str]
@property
@abstractmethod
def image(self) -> str: ...
[docs]
@abstractmethod
def start(self, wd: Path, auth: AuthSpec) -> AgentRun:
"""Launch the agent with the given working directory and credentials.
Parameters
----------
wd : pathlib.Path
The populated agent working directory.
auth : AuthSpec
Credential-forwarding spec from the harness.
Returns
-------
AgentRun
Handle to the in-flight run.
"""
...