Skip to content

Adapter Lifecycle

Every adapter in the OpenFrame v3 ecosystem follows the same four-phase lifecycle managed by PluginRegistry: initialize, health, execute, shutdown. Health is a single Lifecycle.health() -> PluginHealth call — the old separate ping()/is_ready() pair is gone.


Lifecycle Phases

stateDiagram-v2
    direction LR
    [*] --> Registered : registry.register(port, config)
    Registered --> Initialising : registry.initialize_all()
    Initialising --> Healthy : port.initialize(PluginContext)\nport.health() → READY
    Initialising --> Failed : AdapterConnectionError\nor PluginInitializationError
    Healthy --> Serving : execute operations
    Serving --> Serving : operations succeed
    Serving --> Reconnecting : driver detects connection loss
    Reconnecting --> Serving : pool replaces connection
    Reconnecting --> Failed : reconnect exhausted
    Serving --> Closed : registry.shutdown_all() (LIFO)
    Failed --> [*]
    Closed --> [*]

Reconnect Safety

When a connection pool replaces a broken connection, the driver may replace its own internal method objects. TracingProxy is designed for this — it resolves the method via getattr(wrapped, name) on every async call, never from a snapshot captured at first access.

async def _traced(*args, **kwargs):
    # Re-resolves on every call — never a stale snapshot
    current = getattr(object.__getattribute__(self, "_wrapped"), name)
    with get_tracer().start_as_current_span(f"{prefix}.{name}"):
        return await current(*args, **kwargs)

→ See tracing flow for the full span creation sequence.


Health Contract

Lifecycle.health() must never raise — return PluginHealth(status=PluginStatus.UNAVAILABLE, message="<reason>") on any failure. This is the single health primitive in v3; there is no separate ping() or is_ready().

async def health(self) -> PluginHealth:
    try:
        await self._pool.fetchval("SELECT 1")
        return PluginHealth(status=PluginStatus.READY)
    except Exception as exc:
        return PluginHealth(
            status=PluginStatus.UNAVAILABLE,
            message=str(exc),
        )

Liveness vs. readiness granularity is expressed in PluginStatus and PluginHealth.details, not in separate methods:

# Distinguish readiness detail in PluginHealth.details
return PluginHealth(
    status=PluginStatus.READY,
    details={"ping_ms": 2.1, "schema_ok": True},
)

→ See ports module for PluginHealth, PluginStatus, Lifecycle.