Skip to content
Packages Examples Agents Blog Get started

Boot Sequence

ProviderOrchestrator.boot_all() is the engine behind Application.start(). This page is the contract for writing providers. For states, health, and how to run an app, see Application Lifecycle.

Application.__init__()
└─ Container created; OrideconConfig + Application bound as singletons
add_module / add_provider (must happen while CREATED)
Application.start()
├─ validate config for the active environment
├─ discover_modules() if config.discovery.auto_discover
├─ ModuleCompiler.compile() if any modules are registered
├─ register() on every provider ← ContainerRegistrarProtocol
├─ container.freeze() ← singleton/transient/scoped now raise
├─ container.validate()
├─ boot() on every provider ← BootContainerProtocol
└─ RUNNING

register() and boot() of providers that share a dependency level run concurrently (asyncio.gather()). Levels themselves are serial and follow ProviderPriority plus the dependencies tuple.

add_module() / add_provider() only queue work. The register() call happens later, inside the orchestrator.


Declarative only. Bind protocols to implementations. No I/O, no resolve().

from oridecon import Provider
from oridecon.contracts.core import ProviderPriority
from oridecon.contracts.core.di import ContainerRegistrarProtocol
class DatabaseProvider(Provider):
name = "database"
priority = ProviderPriority.INFRASTRUCTURE
async def register(self, container: ContainerRegistrarProtocol) -> None:
container.singleton(DatabaseProtocol, MySqlConnection)
container.transient(UserRepository, UserRepositoryImpl)

ContainerRegistrarProtocol has singleton(), transient(), scoped(), has(), and bind(). It does not have resolve(). mypy will flag await container.resolve(...) here.

If the provider declares config_key and config_model, the orchestrator injects provider.config from OrideconConfig.get_section() before register() runs.


After every register() returns:

  1. container.freeze() — further singleton() / transient() / scoped() raise ContainerError (ORI_ERR_DI_001).
  2. container.validate() — missing dependencies, cycles, scope violations.
  3. Module export matching.

Any issue raises ContainerValidationError and boot aborts (nothing has connected yet).


Resolve and initialize. This is where you connect, warm caches, wrap instances.

from oridecon.contracts.core.di import BootContainerProtocol
class DatabaseProvider(Provider):
async def boot(self, container: BootContainerProtocol) -> None:
db = await container.resolve(DatabaseProtocol)
await db.connect()

BootContainerProtocol is registrar plus resolver. After freeze, singleton() / transient() / scoped() still raise. To replace an already-registered singleton (wrap a client, inject a connected pool), use container.bind(service_type, instance):

async def boot(self, container: BootContainerProtocol) -> None:
raw = await container.resolve(CacheBackend)
await raw.connect()
container.bind(CacheBackend, InstrumentedCache(raw))

Providers boot in ascending priority:

PriorityValueExample
CRITICAL0Configuration, diagnostics
INFRASTRUCTURE10Database, cache, brokers
SECURITY20Auth
NORMAL30Default
APPLICATION40CLI, admin
DOMAIN50Business logic
PRESENTATION80Web / API
COMMS90Email, SMS, webhooks
LOW100Optional, last

If a required provider’s boot() fails, already-booted providers are shutdown() in reverse order (rollback) and the exception propagates. A provider with required = False logs a warning and the rest of boot continues.

boot_timeout (seconds) wraps boot() in asyncio.wait_for.


When every required provider has booted, on_module_booted() runs for each module. The app then becomes RUNNING. A banner is logged unless ORI_QUIET=1:

╔════════════════════════════════════════════════════════╗
║ Oridecon 0.1.x ║
║ Python 3.12.x ║
║ ║
║ Providers : 12 ║
║ Modules : 4 ║
╚════════════════════════════════════════════════════════╝

app.stop() (or leaving async with Application.boot(...)):

  1. State → STOPPING
  2. on_module_shutdown() on every module
  3. on_before_shutdown lifecycle hooks
  4. shutdown() on providers in reverse boot order (parallel within a level)
  5. container.dispose()
  6. State → STOPPED

Every provider’s shutdown() is called even if one fails. Errors are collected; the first is re-raised after the rest finish. stop() is safe to call more than once.

class DatabaseProvider(Provider):
def __init__(self) -> None:
super().__init__()
self._db: DatabaseProtocol | None = None
async def boot(self, container: BootContainerProtocol) -> None:
self._db = await container.resolve(DatabaseProtocol)
await self._db.connect()
async def shutdown(self) -> None:
if self._db is not None:
await self._db.disconnect()

from oridecon import Provider
from oridecon.contracts.core import ProviderPriority, HealthCheckResult, HealthStatus
from oridecon.contracts.core.di import ContainerRegistrarProtocol, BootContainerProtocol
class CacheProvider(Provider):
name = "cache"
priority = ProviderPriority.INFRASTRUCTURE
dependencies = ("config",)
def __init__(self) -> None:
super().__init__()
self._backend: CacheBackend | None = None
async def register(self, container: ContainerRegistrarProtocol) -> None:
container.singleton(CacheBackend, RedisCache)
async def boot(self, container: BootContainerProtocol) -> None:
self._backend = await container.resolve(CacheBackend)
await self._backend.connect()
async def shutdown(self) -> None:
if self._backend is not None:
await self._backend.disconnect()
async def health_check(self, timeout: float = 5.0) -> HealthCheckResult:
return HealthCheckResult(component=self.name, status=HealthStatus.HEALTHY)

The container exists from Application.__init__(), but it only has the app, config, and invoker until providers register. Resolving your services first raises UnresolvableDependencyError (ORI_ERR_DI_004).

app = Application()
await app.container.resolve(DatabaseProtocol) # ORI_ERR_DI_004

Resolve inside boot(), or after start() / inside async with Application.boot(...).

app = Application()
await app.start()
app.add_module(ExtraModule) # RuntimeError: Cannot add_module after boot

Pass them to Application.boot(modules=..., providers=...) or call add_* while CREATED.

ContainerRegistrarProtocol has no resolve(). Bind in register(), connect in boot().

BootContainerProtocol structurally includes registrar methods, but the container is already frozen. singleton() during boot() raises ContainerError. Use bind() to replace a singleton that you registered in phase 1.