agentleFS
dbtinoy-dbtinoy-/lexigram-framework-skills/creating-providers-and-modules/SKILL.md

creating-providers-and-modules

Skill1 starsChanged 3 months agoMITView on GitHub

Use when creating new extension packages, adding providers, or defining modules in the Lexigram framework

The file

We split this skill into the rules it gives an agent

5 rules. About 0 are common, 2 are distinctive, and 3 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    No business logic on Provider classes

    line 47
  2. Distinctive:

    All I/O in boot/shutdown is async

    line 48
  3. Very specific:

    Never share a Subscription across providers — always unsubscribe in shutdown().

    line 126
  4. Very specific:

    Not defining stub() — forces test modules to use real implementations

    line 134
  5. Very specific:

    Not unsubscribing in shutdown() — subscription handles leak, consumers pile up

    line 136

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Creating Providers and Modules

Overview

Providers wire services into the DI container. Modules group providers and enforce visibility.

Core Pattern

Provider = registers/boots/shuts down one bounded concern. Module = groups providers, defines imports/exports.

Provider Lifecycle

from lexigram.di import Provider
from lexigram.contracts.core.di import ContainerRegistrarProtocol, BootContainerProtocol
from lexigram.contracts.core.health import HealthCheckResult, HealthStatus

class MyProvider(Provider):
    name = "my_provider"
    priority = ProviderPriority.NORMAL

    async def register(self, container: ContainerRegistrarProtocol) -> None:
        container.singleton(MyProtocol, MyImpl)
        container.transient(OtherProtocol, lambda: OtherImpl(...))

    async def boot(self, container: BootContainerProtocol) -> None:
        svc = await container.resolve(MyProtocol)
        await svc.connect()

    async def shutdown(self) -> None:
        await self._cleanup()

    async def health_check(self, timeout: float = 5.0) -> HealthCheckResult:
        return HealthCheckResult(component=self.name, status=HealthStatus.HEALTHY)

Rules

  • register() gets ContainerRegistrarProtocol — binds only, no resolution
  • boot() gets BootContainerProtocol — may resolve AND register
  • Optional hooks: on_error(error, phase), shutdown(), health_check(timeout)
  • No business logic on Provider classes
  • All I/O in boot/shutdown is async

Provider Priorities

PriorityValueUse
CRITICAL0Logging, errors
INFRASTRUCTURE10DB, cache, queues
SECURITY20Auth, encryption
NORMAL30Default
APPLICATION40CLI, admin utilities
DOMAIN50Business services
PRESENTATION80Web/API entry points
COMMS90Outbound email/SMS/webhooks
LOW100Optional, boot last

Shutdown runs in reverse order.

Module Patterns

Static Module (no config)

from lexigram.di import module, Module

@module(providers=[MyProvider], imports=[ConfigModule], exports=[MyProtocol])
class MyModule(Module):
    pass

Dynamic Module (with config)

from lexigram.di import module, Module, DynamicModule

@module()
class MyModule(Module):
    @classmethod
    def configure(cls, config: MyConfig | None = None) -> DynamicModule:
        return DynamicModule(
            module=cls,
            providers=[MyProvider(config=config)],
            exports=[MyProtocol],
        )

    @classmethod
    def stub(cls, config: MyConfig | None = None) -> DynamicModule:
        return DynamicModule(
            module=cls,
            providers=[MyStubProvider(config=config)],
            exports=[MyProtocol],
        )

Three factory conventions: configure(), scope(), stub().

Package File Layout

src/lexigram/mypackage/    __init__.py (lazy exports), config.py,
                           module.py, exceptions.py, di/provider.py
tests/                     pyproject.toml

Shared Queue Lifecycle

Coordinate subscribe/unsubscribe when multiple providers share a queue:

class WorkerProvider(Provider):
    async def boot(self, container):
        queue = await container.resolve(QueueProtocol)
        self.sub = await queue.subscribe("orders.new", self.handle)

    async def shutdown(self):
        await self.sub.unsubscribe()   # each provider owns its handles

Never share a Subscription across providers — always unsubscribe in shutdown().

Common Mistakes

  • Registering after container freeze — raises ContainerError
  • Resolving in register() — use boot() instead
  • Business logic on Provider — belongs in services, not providers
  • Forgetting exports in DynamicModule — consumers can't resolve the type
  • Not defining stub() — forces test modules to use real implementations
  • Declaring config_key without config_model — raises ConfigurationError at boot unless a LexigramConfig is registered or provider.config is set directly
  • Not unsubscribing in shutdown() — subscription handles leak, consumers pile up
  • Sharing a Subscription across providers — race on unsubscribe tears down other consumers

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.