StoryMatrix system design answers how domain rules stay independent while application workflows connect to infrastructure and CrewAI orchestration.

🧱 Layers and dependency rule

LayerResponsibilityAllowed dependencies
DomainEntities, value objects, enums, domain services, exceptions, and repository contractsNothing outside the domain and standard-library dependencies (src/storymatrix/domain/)
ApplicationUse cases, production stages, DTOs, service ports, and orchestration of business workflowsDomain only (src/storymatrix/application/)
InfrastructureDatabase repositories, external-provider adapters, media services, and concrete service implementationsDomain and Application (src/storymatrix/infrastructure/)
CrewLLM-oriented agents, tasks, prompts, schemas, and crew runnersOrchestration that supplies application workflows (src/storymatrix/crew/)

The dependency direction keeps domain policy independent from providers and runtime frameworks. Crew is the LLM orchestration layer rather than a replacement for domain or application policy.

🔌 Ports and adapters

Application ports define capabilities consumed by use cases and stages in application/interfaces/services.py, including story orchestration, timeline generation, audio generation and production, image generation, voice services, and other application-facing services. Infrastructure supplies concrete adapters and services for those ports in infrastructure/adapters/ and infrastructure/services/.

Domain ports define persistence contracts in domain/interfaces/repositories.py. Infrastructure implements those contracts with SQLite repositories and in-memory repositories in infrastructure/repositories/. The application calls abstractions; the container selects concrete implementations.

🧩 Dependency injection

infrastructure/container.py defines ServiceContainer as a dependency-injector declarative container. Providers include Singleton, Factory, Selector, Callable, and Configuration providers. Selector keys choose adapters from configuration and local-development gates.

The container keeps heavy imports inside provider factories instead of importing them during module startup. A concrete pattern is the montage factory:

def ffmpeg_montage_service_factory(config, temp_dir):
    from storymatrix.infrastructure.services.ffmpeg_montage import FFmpegMontageService
    from storymatrix.config.models import MontageConfig
 
    montage_config_dict = _safe_config_get(config, "services.montage", {})
    ffmpeg_path = _safe_config_get(config, "paths.ffmpeg", "/usr/bin/ffmpeg")
    ffprobe_path = _safe_config_get(config, "paths.ffprobe", "/usr/bin/ffprobe")
    montage_config = MontageConfig(**montage_config_dict)
    return FFmpegMontageService(
        config=montage_config,
        ffmpeg_path=ffmpeg_path,
        ffprobe_path=ffprobe_path,
    )

The same pattern appears in factories for image, TTS, SFX, music, and audio-search adapters. Lazy inner imports prevent startup crashes from optional C-extension stacks such as torch and pydub on CPU-only hosts, while allowing the selected provider to load when its provider is constructed (src/storymatrix/infrastructure/container.py).

🕸️ Story aggregate

A Story owns ordered Scene objects; each scene owns ordered Segment objects, while characters connect to dialogue segments through character identity (src/storymatrix/domain/entities/story.py, src/storymatrix/domain/entities/scene.py, src/storymatrix/domain/entities/segment.py).

flowchart TD
    Story --> Scene
    Scene --> Segment
    Story --> Character
    Segment -->|dialogue reference| Character

The segment union contains four subtypes—narration, dialogue, sound effects, and music. Image content is a separate entity rather than a segment subtype (src/storymatrix/domain/entities/segment.py, src/storymatrix/domain/entities/image.py).

🗄️ SQLite persistence

StoreContentRepository wiring
data/storymatrix.dbStories, characters, scenes, segments, their relationships, and media-asset metadataSQLiteStoryRepository, SQLiteCharacterRepository, and FileSystemMediaAssetRepository (the latter uses its data/storymatrix.db config default) (src/storymatrix/infrastructure/database/models.py, src/storymatrix/infrastructure/container.py, src/storymatrix/config/models.py)
data/audio_metadata.dbCharacter-to-voice mappingsSQLiteCharacterVoiceMappingRepository (src/storymatrix/infrastructure/database/models.py, src/storymatrix/infrastructure/container.py)

The content schema uses stories, characters, scenes, segments, and character_voice_mappings tables; segment rows use a single-table polymorphic shape keyed by segment_type (src/storymatrix/infrastructure/database/models.py).

🛡️ Domain isolation guard

tests/unit/domain/test_domain_isolation.py parses every domain Python file and rejects imports whose module name starts with any of these exact prefixes:

storymatrix.config
storymatrix.application
storymatrix.infrastructure
storymatrix.crew

The same test checks that constructing StoryTemplates does not call builtins.open, keeping domain template objects free from direct filesystem access (tests/unit/domain/test_domain_isolation.py).