# `MingaAgent.Session.ProviderLifecycle`
[🔗](https://github.com/jsmestad/minga/blob/main/lib/minga_agent/session/provider_lifecycle.ex#L1)

Owns provider identity, configuration, process attachment, and retry state for one agent session.

Transitions are pure. `MingaAgent.Session` remains responsible for provider calls, process monitoring, timers, and code lease effects.

# `effect`

```elixir
@type effect() ::
  {:cancel_timer, reference()}
  | {:release_lease, Minga.Extension.CodeLease.t()}
  | {:stop_provider, pid()}
```

External effect for the Session process to execute after a transition.

# `effects`

```elixir
@type effects() :: [effect()]
```

Ordered external effects returned by a pure lifecycle transition.

# `phase`

```elixir
@type phase() ::
  {:stopped, Minga.Extension.CodeLease.t() | nil, retry_state()}
  | {:starting, Minga.Extension.CodeLease.t() | nil, retry_state()}
  | {:running, pid(), Minga.Extension.CodeLease.t() | nil, retry_state()}
  | {:retrying, retry_state(), retry_timer() | nil}
  | {:terminal_failure, retry_state()}
```

Current provider lifecycle phase with phase-specific state.

# `restart_policy`

```elixir
@type restart_policy() :: %{
  base_delay_ms: pos_integer(),
  max_attempts: pos_integer(),
  max_delay_ms: pos_integer(),
  window_ms: non_neg_integer()
}
```

Provider restart backoff policy.

# `retry_state`

```elixir
@type retry_state() :: %{
  attempts: non_neg_integer(),
  window_started_at_ms: integer() | nil,
  failure_reason: term()
}
```

Provider retry history retained across process replacements.

# `retry_timer`

```elixir
@type retry_timer() :: {timer_ref :: reference(), token :: reference()}
```

Retry timer identity installed by the Session process.

# `t`

```elixir
@type t() :: %MingaAgent.Session.ProviderLifecycle{
  id: String.t(),
  model_name: String.t(),
  module: module(),
  opts: keyword(),
  phase: phase(),
  provider_name: String.t(),
  restart_policy: restart_policy(),
  source: Minga.Extension.ContributionCleanup.contribution_source()
}
```

Provider lifecycle state owned by a session.

# `attach`

```elixir
@spec attach(t(), pid()) :: {t(), effects()}
```

Attaches a started provider process.

# `failure`

```elixir
@spec failure(t(), term()) :: {t(), effects()}
```

Records provider failure and detaches the failed process and lease.

# `failure_reason`

```elixir
@spec failure_reason(t()) :: term()
```

Returns the most recent provider failure reason.

# `install_lease`

```elixir
@spec install_lease(t(), Minga.Extension.CodeLease.t()) ::
  {:ok, t()} | {:invalid_phase, t()}
```

Installs a source-code lease acquired while provider startup is in progress.

# `install_retry_timer`

```elixir
@spec install_retry_timer(t(), reference(), reference()) ::
  {:ok, t()} | {:invalid_phase, t()}
```

Installs the timer identity created by the Session process for a scheduled retry.

# `is_detached`
*macro* 

Returns true in guards when no provider process is attached.

# `lease`

```elixir
@spec lease(t()) :: Minga.Extension.CodeLease.t() | nil
```

Returns the active provider code lease, or nil when no provider is attached.

# `new`

```elixir
@spec new(keyword()) :: t()
```

Builds the initial lifecycle value from resolved provider configuration.

# `phase`

```elixir
@spec phase(t()) :: :stopped | :starting | :running | :retrying | :terminal_failure
```

Returns the current lifecycle phase name.

# `pid`

```elixir
@spec pid(t()) :: pid() | nil
```

Returns the active provider process, or nil when no provider is attached.

# `replace`

```elixir
@spec replace(t(), String.t(), String.t(), keyword()) :: t()
```

Replaces the model configuration while preserving process and retry identity.

# `reset_retry`

```elixir
@spec reset_retry(t()) :: {t(), effects()}
```

Clears retry history for an explicit provider restart.

# `retry`

```elixir
@spec retry(t(), term(), integer()) ::
  {:retry, t(), pos_integer(), effects()} | {:terminal_failure, t(), effects()}
```

Calculates the next retry or records terminal retry exhaustion.

# `retry_attempts`

```elixir
@spec retry_attempts(t()) :: non_neg_integer()
```

Returns the number of retries attempted in the current backoff window.

# `retry_due`

```elixir
@spec retry_due(t(), reference()) :: {:start, t()} | {:stale, t()}
```

Consumes a matching retry timer token and rejects stale timer messages.

# `retry_timer`

```elixir
@spec retry_timer(t()) :: retry_timer() | nil
```

Returns the installed retry timer identity, if a retry is scheduled.

# `retry_window_started_at_ms`

```elixir
@spec retry_window_started_at_ms(t()) :: integer() | nil
```

Returns the start of the current retry window.

# `start`

```elixir
@spec start(t()) :: {:start, t(), effects()} | {:active, t(), effects()}
```

Begins provider startup unless a provider is already attached.

# `stop`

```elixir
@spec stop(t()) :: {t(), effects()}
```

Stops the lifecycle and clears attached process, lease, retry, and failure state.

# `terminal_failure?`

```elixir
@spec terminal_failure?(t()) :: boolean()
```

Returns true when automatic retries are exhausted.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
