Skip to content

EngineManager

The EngineManager class manages the lifecycle of the attest-engine Go subprocess. It handles binary discovery, process spawning, JSON-RPC communication, and graceful shutdown.

from attest.engine_manager import EngineManager
async with EngineManager() as engine:
result = await engine.send_request("evaluate_batch", params)

In practice, the pytest plugin manages the engine automatically via the attest_engine fixture. Direct usage of EngineManager is needed for custom integrations or the ContinuousEvalRunner.

EngineManager(
engine_path: str | None = None,
log_level: str = "warn",
)
ParameterTypeDefaultDescription
engine_pathstr | NoneNoneExplicit path to the attest-engine binary. When None, uses the discovery chain.
log_levelstr"warn"Engine log verbosity: debug, info, warn, error

When engine_path is not provided, the SDK searches for the binary in this order:

PrioritySourceDetails
1ATTEST_ENGINE_PATH env varAbsolute path override. Raises FileNotFoundError if the file does not exist.
2PATH lookupUses shutil.which("attest-engine")
3~/.attest/bin/Shared cache with version check via .engine-version marker file
4Monorepo layout../../bin/attest-engine relative to the SDK package
5Local ./bin/./bin/attest-engine in the current working directory
6Auto-downloadDownloads from GitHub Releases with SHA256 verification
7ErrorFileNotFoundError with actionable install instructions

When no binary is found, the SDK downloads it from GitHub Releases:

  1. Fetches checksums-sha256.txt for the target ENGINE_VERSION
  2. Downloads the platform-specific binary (darwin-arm64, linux-amd64, etc.)
  3. Verifies SHA256 checksum
  4. Atomic write to ~/.attest/bin/attest-engine
  5. Writes .engine-version marker for cache validation

Supported platforms:

OSArchitectureAsset Name
macOSARM64 (Apple Silicon)attest-engine-darwin-arm64
macOSx86_64 (Intel)attest-engine-darwin-amd64
LinuxARM64attest-engine-linux-arm64
Linuxx86_64attest-engine-linux-amd64
WindowsAMD64attest-engine-windows-amd64.exe
WindowsARM64attest-engine-windows-arm64.exe

Spawns the engine subprocess and sends the initialize JSON-RPC request.

engine = EngineManager()
init_result = await engine.start()
print(init_result.engine_version) # "0.4.0"
print(init_result.capabilities) # ["layers_1_4", ...]
print(init_result.max_concurrent_requests) # 64
print(init_result.max_trace_size_bytes) # 10485760
print(init_result.max_steps_per_trace) # 10000

Raises RuntimeError if the engine reports incompatible capabilities.

Sends the shutdown JSON-RPC request, then terminates the process.

await engine.stop()

If the shutdown request fails, the process is terminated. If termination times out after 5 seconds, the process is killed.

The recommended pattern. start() is called on __aenter__, stop() on __aexit__.

async with EngineManager(log_level="debug") as engine:
# Engine is initialized and ready
result = await engine.send_request("evaluate_batch", params)
# Engine is stopped

send_request(method: str, params: dict) -> Any

Section titled “send_request(method: str, params: dict) -> Any”

Send a JSON-RPC 2.0 request and return the result.

result = await engine.send_request("evaluate_batch", {
"trace": trace.to_dict(),
"assertions": [a.to_dict() for a in assertions],
})

Raises RuntimeError if the engine has not been initialized (except for the initialize method itself).

Property that returns True if the subprocess is alive.

The response from the engine’s initialize method:

FieldTypeDescription
engine_versionstrEngine binary version (e.g., "0.4.0")
protocol_versionintProtocol version (currently 1)
capabilitieslist[str]Supported capability strings
missinglist[str]Requested capabilities not available
compatibleboolTrue if all required capabilities are present
encodingstrWire encoding ("json")
max_concurrent_requestsintMax parallel evaluations (default 64)
max_trace_size_bytesintMax trace payload (default 10 MB)
max_steps_per_traceintMax steps per trace (default 10000)

The engine lifecycle is handled by the attest_engine session-scoped fixture:

# conftest.py — no manual setup needed
# The attest pytest plugin auto-registers fixtures
def test_agent(attest):
result = my_agent(query="test")
chain = expect(result).output_contains("test")
agent_result = attest.evaluate(chain)
assert agent_result.passed

The fixture accepts CLI options:

OptionDefaultDescription
--attest-engineauto-discoverPath to engine binary
--attest-log-levelwarnEngine log level
--attest-tierallOnly run tests up to this tier
--attest-budgetunlimitedAbort if cumulative cost exceeds USD amount
--attest-cost-reportoffPrint cost summary at session end
VariableDescription
ATTEST_ENGINE_PATHOverride binary location (takes highest priority)
ATTEST_ENGINE_NO_DOWNLOADSet to 1 to disable auto-download
ATTEST_SIMULATIONSet to 1 to skip engine entirely (deterministic results)

The SDK version (__version__) and engine version (ENGINE_VERSION) are independent. SDK 0.4.2 ships with ENGINE_VERSION = "0.4.0". The auto-downloader uses ENGINE_VERSION to construct the GitHub release URL, not the SDK version. This allows SDK-only releases without requiring new engine binaries.

from attest import __version__, ENGINE_VERSION
print(__version__) # "0.4.2"
print(ENGINE_VERSION) # "0.4.0"