///|
/// Lifecycle: the extension-facing agent lifecycle, three phases.
///
/// - `on_compose`: called once, synchronously, after every composition gate
/// has passed (catalog atomic snapshot, model cardinality, collision
/// checks) and before the Agent is ready. This is where the composed
/// capabilities declared in the manifest's `requires` are delivered via
/// `CompositionView`. Sync on purpose: `ModelPort::chat` and
/// `UiPort::request` are async, so a sync composition point cannot *use*
/// what it wires — the type system enforces "wire, don't act". Raise
/// `CompositionError::ExtensionComposeFailed` to fail the composition
/// loudly; no partial Agent is produced.
/// - `on_start`: called once per Agent lifetime, inside the first
/// `run_turn` (an async context), before the first `TurnStarted` is
/// projected. This is the legitimate birth point for loading persisted
/// state or spawning background loops — construction time has no async
/// root. Not re-fired after `Suspended` resumes. Like other async slots
/// the body may raise; the Agent wraps a raised error loudly and the
/// first turn fails before any terminal event is emitted.
/// - `on_shutdown`: resource cleanup hook, called once on agent shutdown in
/// reverse registration order; cleanup failure propagates (loud).
///
/// All methods have defaults: an extension overrides only the phases it
/// cares about, and adding a phase is non-breaking (same pattern as `PipelineHook`).
pub(open) trait Lifecycle {
fn on_compose(Self, ctx : CompositionView) -> Unit raise @error.CompositionError = _
async fn on_start(Self) -> Unit = _
async fn on_shutdown(Self) -> Unit
}
///|
/// Default `on_compose`: no wiring needed.
impl Lifecycle with fn on_compose(_self, _ctx : CompositionView) -> Unit {
()
}
///|
/// Default `on_start`: nothing to boot. Sync body satisfies the async slot.
impl Lifecycle with fn on_start(_self) -> Unit {
()
}