Skip to content
GitHub

Architecture

Internal design of the lexigram-ai package — the AI orchestrator for the Lexigram Framework.


lexigram-ai is an optional orchestrator that discovers and coordinates AI sub-packages via Python entry points. It does not implement LLM clients, RAG pipelines, or agent strategies — it registers, wires, and monitors them.

flowchart LR
    subgraph User[Application Code]
        C["@module(imports=[AIModule.configure(...)])"]
    end

    subgraph AI[lexigram-ai Orchestrator]
        AM[AIModule]
        AP[AIProvider]
    end

    subgraph Subsystems[Discovered via entry points]
        LLM[lexigram-ai-llm]
        RAG[lexigram-ai-rag]
        AGENTS[lexigram-ai-agents]
        MEM[lexigram-ai-memory]
        SKILL[lexigram-ai-skills]
        SESSION[lexigram-ai-session]
        FEEDBACK[lexigram-ai-feedback]
        WORKERS[lexigram-ai-workers]
        MCP[lexigram-ai-mcp]
        OBSERV[lexigram-ai-observability]
    end

    subgraph Optional[Optional deps]
        VECTOR[lexigram-vector]
    end

    User -->|AIModule.configure| AM
    AM -->|exports AIProviderProtocol| AP
    AP -->|register / boot / shutdown| LLM
    AP -->|register / boot / shutdown| RAG
    AP -->|register / boot / shutdown| AGENTS
    AP -.->|entry-point scan| MEM
    AP -.->|entry-point scan| SKILL
    AP -.->|entry-point scan| SESSION
    AP -.->|entry-point scan| FEEDBACK
    AP -.->|entry-point scan| WORKERS
    AP -.->|entry-point scan| MCP
    AP -.->|entry-point scan| OBSERV
    RAG -.-> VECTOR

Key constraint: Sub-packages import only from lexigram and lexigram-contracts. They never import from lexigram-ai or from each other. Cross-package communication goes through contracts resolved via the DI container.


lexigram-ai discovers sub-packages through two entry-point groups:

Entry Point GroupPurposeLoaded By
lexigram.ai.subsystemsRuntime discovery at AIProvider.register()AIProvider.register()
lexigram.ai.modulesTest-time only — AIModule.stub()AIModule.stub()
flowchart TD
    subgraph Registration[AIProvider.register]
        EP[Scan lexigram.ai.subsystems groups] --> FILTER{name in}
        FILTER -->|llm / vector / rag| EXPLICIT["Wire with config injection"]
        FILTER -->|other| DYNAMIC["Load & call .register(container)"]
        EXPLICIT --> LLM["LLMProvider.register(container)"]
        EXPLICIT --> VEC["VectorProvider.register(container)"]
        EXPLICIT --> RAG["RAGProvider.register(container)"]
    end

    subgraph EP_Contract[Sub-package declares]
        TOML[pyproject.toml] -->|entry-points.lexigram.ai.subsystems| EP
    end

    TOML -->|example| EG[
        "[project.entry-points."lexigram.ai.subsystems"]"
        "agents = "lexigram.ai.agents.di.provider:AgentsProvider""
    ]

The three core subsystems (LLM, Vector, RAG) are handled explicitly with config injection (provider.py:210-231). All others enter a dynamic loop (provider.py:254-274) that loads the provider class and calls register(), passing config from a lookup dict keyed by entry-point name.


AIModule is the public entry point — a @module()-decorated class whose configure() returns a DynamicModule registering AIProvider and exporting AIProviderProtocol. Consumers pass the result to their own @module(imports=[...]).

flowchart BT
    subgraph Public[Public API]
        INIT["__init__.py<br/>Lazy re-exports"]
        MOD["module.py<br/>AIModule"]
    end

    subgraph DI[DI Layer]
        PROVIDER["di/provider.py<br/>AIProvider"]
        FACTORIES["di/factories.py<br/>Factories"]
    end

    subgraph Config[Configuration]
        CONFIG["config.py<br/>AIConfig"]
        CONST["constants.py<br/>Defaults"]
    end

    subgraph Shared[Shared]
        TYPES["types.py"]
        PROTO["protocols.py"]
        EXC["exceptions.py"]
        EVT["events.py"]
        HOOKS["hooks.py"]
        DECO["decorators.py"]
    end

    subgraph Owned[Aggregated Services]
        OBSERV[observability/<br/>Metrics · Tracing · Health]
        GOVERNANCE[governance/<br/>Audit · Policy]
        FEEDBACK[feedback/<br/>Processor registry]
    end

    subgraph CLI[CLI]
        CLI_DIR["cli/<br/>commands · checks · doctor · shell"]
    end

    MOD --> PROVIDER
    CONFIG --> PROVIDER
    CONST --> CONFIG
    PROVIDER --> OBSERV
    PROVIDER --> GOVERNANCE
    PROVIDER --> FEEDBACK
    PROVIDER --> CLI_DIR
PathPurpose
lexigram/ai/__init__.pyLazy re-exports (AIConfig, AIModule, AIProvider)
lexigram/ai/module.pyAIModule@module() entry point
lexigram/ai/di/provider.pyAIProvider orchestrator
lexigram/ai/di/factories.pyReserved factory functions
lexigram/ai/config.pyAIConfig with try/except optional imports
lexigram/ai/constants.pyENV_PREFIX, default limits, version
lexigram/ai/types.pyAIBaseEvent, contracts re-exports
lexigram/ai/exceptions.pyAIError (package base, code LEX_ERR_AI_005)
lexigram/ai/observability/Health, Metrics, Tracing, CallbackManager
lexigram/ai/governance/Audit store, policy manager, persistence backends
lexigram/ai/feedback/FeedbackProcessorRegistry
lexigram/ai/cli/Commands, health checks, doctor, shell

AIProvider follows the standard Lexigram three-phase lifecycle:

class AIProvider(Provider, AIProviderProtocol):
name = "ai"
priority = ProviderPriority.DOMAIN # 50
config_key: str | None = "ai"
config_model: type | None = AIConfig
optional_dependencies: tuple[str, ...] = ("db", "cache")
sequenceDiagram
    participant App as Application
    participant Container as DI Container
    participant AI as AIProvider
    participant Sub as Sub-Providers
    participant Resolved as Resolved Dependencies

    App->>AI: AIProvider(config=...)
    AI->>AI: __init__()
    App->>Container: freeze()

    Note over AI: ── register() ──
    Container->>AI: register(registrar)
    AI->>AI: Register AIConfig, monitoring, FeedbackProcessorRegistry
    alt governance enabled
        AI->>AI: Register AIAuditStore, AIGovernanceManager
    end
    AI->>Sub: LLMProvider.register()
    AI->>Sub: VectorProvider.register()
    AI->>Sub: RAGProvider.register()
    AI->>AI: Scan entry points → register each

    Note over AI: ── boot() ──
    Container->>AI: boot(resolver)
    AI->>Resolved: resolve(DatabaseProviderProtocol) — optional
    AI->>Resolved: resolve(CacheBackendProtocol) — optional
    alt cache available
        AI->>AI: Init RAGCache
    end

    Note over AI: ── shutdown() ──
    App->>AI: shutdown()
    AI->>Sub: shutdown() each sub-provider
    AI->>AI: Clear references
PhaseMethodReceivesActions
Registrationregister(container)ContainerRegistrarProtocolRegisters config, monitoring, governance singletons; delegates to sub-providers; discovers entry-point subsystems
Bootboot(container)ContainerResolverProtocolResolves optional DatabaseProviderProtocol and CacheBackendProtocol; inits RAGCache
Shutdownshutdown()Calls shutdown() on each sub-provider; clears references

Sub-providers run their lifecycle during AIProvider.register() — they do not get independent lifecycle phases. AIProvider.boot() resolves cross-cutting dependencies; AIProvider.shutdown() iterates sub-providers for cleanup.

health_check() aggregates sub-provider health into a HealthCheckResult with per-component latency, delegating to each sub-provider’s own health_check() method.


AIConfig uses try/except imports so missing sub-packages degrade gracefully:

flowchart LR
    subgraph Fields[AIConfig Fields]
        LLM_F["llm: ClientConfig | None"]
        VEC_F["vector: VectorConfig | None"]
        RAG_F["rag: RAGConfig | None"]
        OBS_F["observability: ObservabilityConfig"]
        SUBS_F["subsystems: dict[str, dict]"]
    end

    LLM_F -->|try import| P1[lexigram-ai-llm]
    RAG_F -->|try import| P2[lexigram-ai-rag]
    VEC_F -->|try import| P3[lexigram-vector]
    OBS_F -->|try import| P4[lexigram-ai-observability]

    P1 -->|import fails| N1["ClientConfig = None"]
    P1 -->|import succeeds| Y1["Full config model"]
    N1 --> SENTINEL["_DisabledSubsystem()<br/>enabled = False"]

When a sub-package is not installed, its config field becomes _DisabledSubsystem() — a falsy sentinel returning None for all attribute access. The provider gates sub-provider delegation on if intelligence_config.llm:.


ContractSourceBound By
AIProviderProtocollexigram.contracts.aiAIProvider
LLMClientProtocollexigram.contracts.ai.llmDelegated to lexigram-ai-llm
VectorStoreProtocollexigram.contracts.data.vectorDelegated to lexigram-vector
RAGPipelineProtocollexigram.contracts.ai.ragDelegated to lexigram-ai-rag
CacheBackendProtocollexigram.contracts.cacheResolved at boot() — optional
DatabaseProviderProtocollexigram.contracts.dataResolved at boot() — optional
CallbackManagerProtocollexigram.contracts.ai.callbacksCallbackManagerImpl
AIHealthMonitorProtocollexigram.contracts.ai.observabilityAIHealthMonitor
AIMetricsProtocollexigram.contracts.ai.observabilityAIMetrics
AITracerProtocollexigram.contracts.ai.observabilityAITracer
AgentExecutorProtocollexigram.contracts.agentsDelegated to lexigram-ai-agents
SessionManagerProtocollexigram.contracts.ai.sessionDelegated to lexigram-ai-session
MemoryStoreProtocollexigram.contracts.ai.memoryDelegated to lexigram-ai-memory
SkillRegistryProtocollexigram.contracts.ai.skillsDelegated to lexigram-ai-skills
FeedbackProtocollexigram.contracts.ai.feedbackDelegated to lexigram-ai-feedback

AICliContributor registers with the lexigram CLI framework:

ContributionDescription
ai command grouplexigram ai ... — subsystem management
ai_llm_provider health checkVerifies LLM provider reachable
ai_subsystem_discovery health checkVerifies entry-point discovery works
ai_api_keys_configured doctor checkChecks AI API keys are configured
ai shell contextInteractive AI client for REPL

PointMechanismExample
New AI subsystemlexigram.ai.subsystems entry pointagents = "lexigram.ai.agents.di.provider:AgentsProvider"
New LLM providerRegister in lexigram-ai-llm’s ProviderRegistryProviderRegistry.register("openai", OpenAIProvider)
New RAG chunking strategylexigram.chunking.strategies entry pointsemantic = "..."
New agent strategylexigram.agent.strategies entry pointreact = "..."
Feedback processorFeedbackProcessorRegistry.register()processor_registry.register("sentiment", ...)
Governance backendImplement persistence for AIGovernanceManagerDatabaseGovernancePersistence
Observability extensionImplement AITracerProtocol / AIMetricsProtocolCustom tracing backend

To add a new subsystem: create a provider with register()/boot()/shutdown(), declare the lexigram.ai.subsystems entry point in pyproject.toml, and configuration flows through AIConfig.subsystems[name]. If the subsystem exposes a protocol consumed by other packages, the protocol must live in lexigram-contracts.


SymbolValueDescription
ENV_PREFIXLEX_AI__Env var prefix for AI config
ENV_NESTED_DELIMITER__Nested key delimiter for env vars
DEFAULT_MAX_TOKENS4096Default max tokens per completion
DEFAULT_TEMPERATURE0.7Default temperature
DEFAULT_REQUEST_TIMEOUT_S60Default request timeout
DEFAULT_CONTEXT_WINDOW_MESSAGES50Max messages in context
__version__0.1.1Package version