⚙️ Configuration

This page answers how StoryMatrix builds StoryMatrixConfig and resolves each setting.

🧭 Configuration tree

StoryMatrixConfig has four top-level sections: app, paths, services, and providers (src/storymatrix/config/models.py:StoryMatrixConfig). The tables list every field in those sections; nested model fields use dotted keys.

🧰 app

KeyTypeDefaultEnvironment variable
app.app_namestrStoryMatrixAPP__APP_NAME
app.app_versionstr0.1.0APP__APP_VERSION
app.log_levelstrsafe-log-level factory resultAPP__LOG_LEVEL
app.log_dirPath<repo-root>/logsAPP__LOG_DIR
app.output_dirPath<repo-root>/outAPP__OUTPUT_DIR
app.temp_dirPath<repo-root>/tempAPP__TEMP_DIR
app.cache_dirPath<repo-root>/cacheAPP__CACHE_DIR
app.media_dirPath<repo-root>/mediaAPP__MEDIA_DIR
app.silence_dirPath<repo-root>/assets/silenceAPP__SILENCE_DIR
app.data_dirPath<repo-root>/dataAPP__DATA_DIR
app.dev_local_onlyboolfalseAPP__DEV_LOCAL_ONLY
app.keep_temp_filesboolfalseAPP__KEEP_TEMP_FILES
app.low_resource_modeboolfalseAPP__LOW_RESOURCE_MODE
app.max_tts_concurrencyint2APP__MAX_TTS_CONCURRENCY

out/ is the application output root (src/storymatrix/config/models.py:AppSettings).

🛣️ paths

KeyTypeDefaultEnvironment variable
paths.ffmpegFilePath/usr/bin/ffmpegnone; PathsSettings is a BaseModel
paths.ffprobeFilePath/usr/bin/ffprobenone; PathsSettings is a BaseModel

(src/storymatrix/config/models.py:PathsSettings)

🧱 services

KeyTypeDefaultEnvironment variable
services.api.hoststr0.0.0.0SERVICES__API__HOST
services.api.portint8000SERVICES__API__PORT
services.api.public_base_urlHttpUrl | NonenullSERVICES__API__PUBLIC_BASE_URL
services.api.auth_enabledboolfalseSERVICES__API__AUTH_ENABLED
services.api.api_keySecretStr | NoneunsetSERVICES__API__API_KEY
services.api.rate_limit_enabledboolfalseSERVICES__API__RATE_LIMIT_ENABLED
services.api.rate_limit_requestsint100SERVICES__API__RATE_LIMIT_REQUESTS
services.api.cors_enabledbooltrueSERVICES__API__CORS_ENABLED
services.web.hoststr0.0.0.0SERVICES__WEB__HOST
services.web.portint8080SERVICES__WEB__PORT
services.celery.broker_hoststrlocalhostSERVICES__CELERY__BROKER_HOST
services.celery.broker_portint6379SERVICES__CELERY__BROKER_PORT
services.celery.result_backend_hoststrlocalhostSERVICES__CELERY__RESULT_BACKEND_HOST
services.celery.result_backend_portint6379SERVICES__CELERY__RESULT_BACKEND_PORT
services.celery.broker_urlstr | NoneNone (validator derives effective redis://localhost:6379/0)SERVICES__CELERY__BROKER_URL
services.celery.result_backendstr | NoneNone (validator derives effective redis://localhost:6379/0)SERVICES__CELERY__RESULT_BACKEND
services.celery.eager_modeboolfalseSERVICES__CELERY__EAGER_MODE
services.chroma.hoststrlocalhostSERVICES__CHROMA__HOST
services.chroma.portint8000SERVICES__CHROMA__PORT
services.chroma.collection_namestrstory_contentSERVICES__CHROMA__COLLECTION_NAME
services.database.urlstrsqlite:///data/storymatrix.dbSERVICES__DATABASE__URL
services.database.userstrstorymatrixSERVICES__DATABASE__USER
services.database.passwordSecretStrconfigured secretSERVICES__DATABASE__PASSWORD
services.database.hoststrlocalhostSERVICES__DATABASE__HOST
services.database.portint57757SERVICES__DATABASE__PORT
services.database.dbnamestrstorymatrixSERVICES__DATABASE__DBNAME
services.database.data_dirstrdataSERVICES__DATABASE__DATA_DIR
services.local_assets.min_similarity_scorefloat0.35SERVICES__LOCAL_ASSETS__MIN_SIMILARITY_SCORE
services.local_assets.sfx_library_pathPath<repo-root>/assets/sfxSERVICES__LOCAL_ASSETS__SFX_LIBRARY_PATH
services.local_assets.music_library_pathPath<repo-root>/assets/musicSERVICES__LOCAL_ASSETS__MUSIC_LIBRARY_PATH
services.local_assets.voice_data_pathPath<repo-root>/models/voicesSERVICES__LOCAL_ASSETS__VOICE_DATA_PATH
services.piper.piper_binarystrpiperSERVICES__PIPER__PIPER_BINARY
services.piper.voices_dirstr./models/voices/piperSERVICES__PIPER__VOICES_DIR
services.piper.models_dirstr./models/voices/piperSERVICES__PIPER__MODELS_DIR
services.piper.default_voicestren_US-ljspeech-highSERVICES__PIPER__DEFAULT_VOICE
services.piper.qualitystrhighSERVICES__PIPER__QUALITY
services.piper.speedfloat1.0SERVICES__PIPER__SPEED
services.piper.noise_scalefloat0.667SERVICES__PIPER__NOISE_SCALE
services.piper.noise_wfloat0.8SERVICES__PIPER__NOISE_W
services.piper.timeout_secondsint120SERVICES__PIPER__TIMEOUT_SECONDS
services.piper.enable_gpuboolfalseSERVICES__PIPER__ENABLE_GPU
services.piper.auto_downloadboolfalseSERVICES__PIPER__AUTO_DOWNLOAD
services.sentence_transformer.model_namestrall-MiniLM-L6-v2SERVICES__SENTENCE_TRANSFORMER__MODEL_NAME
services.sentence_transformer.cache_folderstr | NonenullSERVICES__SENTENCE_TRANSFORMER__CACHE_FOLDER
services.coqui.model_namestrtts_models/multilingual/multi-dataset/xtts_v2none; CoquiTTSConfig is a BaseModel
services.coqui.use_gpuboolfalsenone
services.coqui.default_speakerstr | Nonenullnone
services.coqui.default_languagestrennone
services.coqui.default_speaker_wavstr | Nonenullnone
services.coqui.speaker_wav_search_dirslist[str] | Nonenullnone
services.coqui.speaker_samplesdict[str, str] | Nonenullnone
services.montage.target_loudness_dbfsfloat-20.0SERVICES__MONTAGE__TARGET_LOUDNESS_DBFS
services.montage.implementationstrffmpegSERVICES__MONTAGE__IMPLEMENTATION
services.montage.crossfade_duration_msint500SERVICES__MONTAGE__CROSSFADE_DURATION_MS
services.montage.masteringMasteringConfigenabled with model defaultsnone; nested BaseModel
services.montage.mastering.enabledbooltruenone; nested BaseModel
services.montage.mastering.loudness_targetfloat-20.0none
services.montage.mastering.loudness_rangefloat7.0none
services.montage.mastering.true_peakfloat-2.0none
services.montage.mastering.compressor_thresholdfloat-20.0none
services.montage.mastering.compressor_ratiofloat4.0none
services.montage.mastering.compressor_attackfloat0.005none
services.montage.mastering.compressor_releasefloat0.1none
services.montage.mastering.limiter_thresholdfloat-1.0none
services.montage.music_crossfade_enabledboolfalseSERVICES__MONTAGE__MUSIC_CROSSFADE_ENABLED
services.montage.music_crossfade_msint1000SERVICES__MONTAGE__MUSIC_CROSSFADE_MS
services.mixing_profilesMixingProfilesmodel populated from MixingProfiles.PROFILESnone; MixingProfiles is a BaseModel
services.mixing_profiles.narrationMixingProfileSettingsprofile-dependentnone; named field
services.mixing_profiles.dialogueMixingProfileSettingsprofile-dependentnone; named field
services.mixing_profiles.musicMixingProfileSettingsprofile-dependentnone; named field
services.mixing_profiles.sfxMixingProfileSettingsprofile-dependentnone; named field
services.mixing_profiles.transitionMixingProfileSettingsprofile-dependentnone; named field
services.media_repository.storage_rootPath<repo-root>/medianone; FileSystemMediaAssetRepositoryConfig is a BaseModel
services.media_repository.db_pathPath<repo-root>/data/storymatrix.dbnone
services.artifacts.enabledbooltruenone; ArtifactsConfig is a BaseModel
services.llm.open_router.timeoutint30SERVICES__LLM__OPEN_ROUTER__TIMEOUT

(src/storymatrix/config/models.py:ServicesSettings)

🔌 providers

KeyTypeDefaultEnvironment variable
providers.elevenlabs.api_keySecretStr | NoneunsetPROVIDERS__ELEVENLABS__API_KEY
providers.elevenlabs.default_voicestr21m00Tcm4TlvDq8ikWAMPROVIDERS__ELEVENLABS__DEFAULT_VOICE
providers.elevenlabs.model_strategyenumeleven_multilingual_v2PROVIDERS__ELEVENLABS__MODEL_STRATEGY
providers.elevenlabs.model_idstr | NonenullPROVIDERS__ELEVENLABS__MODEL_ID
providers.elevenlabs.stabilityfloat0.5PROVIDERS__ELEVENLABS__STABILITY
providers.elevenlabs.similarity_boostfloat0.8PROVIDERS__ELEVENLABS__SIMILARITY_BOOST
providers.elevenlabs.stylefloat0.0PROVIDERS__ELEVENLABS__STYLE
providers.elevenlabs.use_speaker_boostbooltruePROVIDERS__ELEVENLABS__USE_SPEAKER_BOOST
providers.elevenlabs.speedfloat1.0PROVIDERS__ELEVENLABS__SPEED
providers.elevenlabs.v3_audio_tags_enabledbooltruePROVIDERS__ELEVENLABS__V3_AUDIO_TAGS_ENABLED
providers.elevenlabs.v3_auto_enhance_promptsbooltruePROVIDERS__ELEVENLABS__V3_AUTO_ENHANCE_PROMPTS
providers.elevenlabs.output_formatstrmp3_44100_128PROVIDERS__ELEVENLABS__OUTPUT_FORMAT
providers.elevenlabs.sfx_enabledbooltruePROVIDERS__ELEVENLABS__SFX_ENABLED
providers.elevenlabs.sfx_duration_secondsint10PROVIDERS__ELEVENLABS__SFX_DURATION_SECONDS
providers.elevenlabs.sfx_prompt_influencefloat0.3PROVIDERS__ELEVENLABS__SFX_PROMPT_INFLUENCE
providers.elevenlabs.pronunciation_dictionary_idslist[str][]PROVIDERS__ELEVENLABS__PRONUNCIATION_DICTIONARY_IDS
providers.elevenlabs.optimize_streaming_latencyint0PROVIDERS__ELEVENLABS__OPTIMIZE_STREAMING_LATENCY
providers.elevenlabs.apply_text_normalizationstrautoPROVIDERS__ELEVENLABS__APPLY_TEXT_NORMALIZATION
providers.elevenlabs.max_retriesint3PROVIDERS__ELEVENLABS__MAX_RETRIES
providers.elevenlabs.timeout_secondsint60PROVIDERS__ELEVENLABS__TIMEOUT_SECONDS
providers.freesound.api_keySecretStr | NoneunsetPROVIDERS__FREESOUND__API_KEY
providers.openrouter.api_keySecretStr | NoneunsetPROVIDERS__OPENROUTER__API_KEY
providers.openrouter.base_urlHttpUrlhttps://openrouter.ai/api/v1PROVIDERS__OPENROUTER__BASE_URL
providers.openrouter.default_modelstrgoogle/gemini-2.5-flash-litePROVIDERS__OPENROUTER__DEFAULT_MODEL
providers.openrouter.http_refererstrhttps://noflippinway.lolPROVIDERS__OPENROUTER__HTTP_REFERER
providers.openrouter.x_titlestrNoLoLPROVIDERS__OPENROUTER__X_TITLE
providers.openrouter.temperaturefloat0.7PROVIDERS__OPENROUTER__TEMPERATURE
providers.openrouter.max_tokensint4096PROVIDERS__OPENROUTER__MAX_TOKENS
providers.openrouter.max_retriesint3PROVIDERS__OPENROUTER__MAX_RETRIES
providers.openrouter.timeout_secondsint120PROVIDERS__OPENROUTER__TIMEOUT_SECONDS
providers.dalle.api_keySecretStr | NoneunsetDALLE_API_KEY
providers.dalle.base_urlHttpUrlhttps://api.openai.com/v1DALLE_BASE_URL
providers.dalle.modelstrdall-e-3DALLE_MODEL
providers.dalle.organization_idstr | NonenullDALLE_ORGANIZATION_ID
providers.dalle.max_retriesint3DALLE_MAX_RETRIES
providers.dalle.timeout_secondsint120DALLE_TIMEOUT_SECONDS
providers.midjourney.api_urlstrhost-specific defaultPROVIDERS__MIDJOURNEY__API_URL
providers.midjourney.api_keySecretStr | NoneunsetPROVIDERS__MIDJOURNEY__API_KEY
providers.midjourney.timeout_secondsint240PROVIDERS__MIDJOURNEY__TIMEOUT_SECONDS
providers.midjourney.max_retriesint4PROVIDERS__MIDJOURNEY__MAX_RETRIES
providers.midjourney.bot_typestrMID_JOURNEYPROVIDERS__MIDJOURNEY__BOT_TYPE
providers.midjourney.modeslist[str][FAST]PROVIDERS__MIDJOURNEY__MODES
providers.midjourney.poll_interval_secondsint15PROVIDERS__MIDJOURNEY__POLL_INTERVAL_SECONDS
providers.midjourney.queue_backoff_base_secondsint15PROVIDERS__MIDJOURNEY__QUEUE_BACKOFF_BASE_SECONDS
providers.midjourney.queue_backoff_max_secondsint25PROVIDERS__MIDJOURNEY__QUEUE_BACKOFF_MAX_SECONDS
providers.midjourney.submit_concurrencyint1PROVIDERS__MIDJOURNEY__SUBMIT_CONCURRENCY
providers.midjourney.upsample_enabledboolfalsePROVIDERS__MIDJOURNEY__UPSAMPLE_ENABLED
providers.midjourney.upsample_countint0PROVIDERS__MIDJOURNEY__UPSAMPLE_COUNT
providers.midjourney.upsample_indiceslist[int][]PROVIDERS__MIDJOURNEY__UPSAMPLE_INDICES
providers.playai.api_keySecretStr | NoneunsetPROVIDERS__PLAYAI__API_KEY
providers.playai.user_idstr | NonenullPROVIDERS__PLAYAI__USER_ID
providers.playai.base_urlAnyUrlhttps://api.play.ht/api/v2PROVIDERS__PLAYAI__BASE_URL
providers.playai.default_voicestrs3://voice-cloning-zero-shot/d9ff78ba-d016-47f6-b046-526a0004622e/alice/manifest.jsonPROVIDERS__PLAYAI__DEFAULT_VOICE
providers.playai.qualitystrhighPROVIDERS__PLAYAI__QUALITY
providers.playai.stream_enabledbooltruePROVIDERS__PLAYAI__STREAM_ENABLED
providers.playai.timeout_secondsint60PROVIDERS__PLAYAI__TIMEOUT_SECONDS
providers.playai.max_retriesint3PROVIDERS__PLAYAI__MAX_RETRIES
providers.playai.output_formatstrmp3PROVIDERS__PLAYAI__OUTPUT_FORMAT
providers.musicgen.api_keySecretStr | NoneunsetMUSICGEN_API_KEY
providers.musicgen.model_idstrfacebook/musicgen-smallMUSICGEN_MODEL_ID
providers.musicgen.timeout_secondsint120MUSICGEN_TIMEOUT_SECONDS
providers.pillow.font_pathstrassets/fonts/Roboto-Regular.ttfnone; PillowConfig is a BaseModel
providers.pillow.default_widthint1024none
providers.pillow.default_heightint1024none
providers.ollama.base_urlHttpUrlhttp://localhost:57707none; OllamaProviderConfig is a BaseModel
providers.ollama.modelstrmistral:latestnone
providers.ollama.timeout_secondsint120none
providers.ollama.temperaturefloat0.7none
providers.ollama.top_pfloat1.0none
providers.strategy.llm_providerenumopenrouterPROVIDERS__STRATEGY__LLM_PROVIDER
providers.strategy.agentic_timeline_providerenumopenrouterPROVIDERS__STRATEGY__AGENTIC_TIMELINE_PROVIDER
providers.strategy.tts_providerenumpiperPROVIDERS__STRATEGY__TTS_PROVIDER
providers.strategy.sfx_providerenumlocalPROVIDERS__STRATEGY__SFX_PROVIDER
providers.strategy.music_providerenumlocalPROVIDERS__STRATEGY__MUSIC_PROVIDER
providers.strategy.image_providerenummockPROVIDERS__STRATEGY__IMAGE_PROVIDER

(src/storymatrix/config/models.py:ProvidersSettings)

🔀 Precedence and loading

load_config() resolves one typed settings object in this order (src/storymatrix/config/config.py, src/storymatrix/cli/main.py):

  1. Pydantic defaults and process environment/.env values are loaded.
  2. YAML values are merged over that base.
  3. Explicit typed CLI overrides are applied last, so CLI mode and tuning flags win over YAML collisions.
  4. The resolved object is passed to consumers without a second raw-environment overlay.

Focused precedence evidence closes B15 for the exercised defaults, environment, YAML, and CLI cases. The shipped YAML still documents ordinary application defaults; full generation remains separately scoped.

load_config() no longer writes provider-strategy diagnostics directly to stdout, closing B16 at its named boundary. The loader’s logging and provider selection remain governed by the resolved configuration.

🧪 Worked resolution: app.dev_local_only

LayerValueEvidence
Pydantic defaultfalseAppSettings.dev_local_only
Environment/.envprofile-provided valueStoryMatrixConfig settings
YAML mergeshipped application valuestorymatrix_config.yaml
Explicit CLI --offlinetruetyped CLI override
Final config valuetrue when the flag is suppliedfocused precedence evidence

The explicit CLI override is not disguised as ordinary environment state and wins over a colliding YAML value (src/storymatrix/cli/main.py, src/storymatrix/config/config.py).

⏱️ Worked resolution: services.llm.open_router.timeout

The compatibility setting demonstrates the one post-YAML exception (src/storymatrix/config/models.py:OpenRouterServiceConfig).

LayerValueEvidence
Pydantic default30OpenRouterServiceConfig.timeout
External environment before YAML45 when SERVICES__LLM__OPEN_ROUTER__TIMEOUT=45Pydantic environment loading
YAML merge60 when YAML contains services: {llm: {open_router: {timeout: 60}}}deep_update()
Explicit post-YAML environment override45_apply_env_overrides() reapplies the externally supplied variable

The final value is 45 for that environment/YAML combination. There is no CLI flag for SERVICES__LLM__OPEN_ROUTER__TIMEOUT; only an externally supplied process environment variable receives this post-YAML treatment (src/storymatrix/config/config.py:_apply_env_overrides).

🧩 Compatibility surface

services.llm.open_router is a minimal, test-facing compatibility shim. The canonical provider configuration lives under providers.openrouter; the shim exposes only timeout under services.llm.open_router (src/storymatrix/config/models.py:OpenRouterServiceConfig).

See 🔑 Environment Variables for the complete environment catalogue and 🔌 Integrations for provider selection.