# Configuration Reference ## Purpose Narratio resolves three YAML documents: - `pipeline.yml`: pipeline/runtime settings - `campaign.yml`: campaign identity and stable input defaults - `session.yml`: session identity, metadata, and audio source selection ## Discovery and Selection ### `pipeline.yml` When `--config` is omitted, search order is: 1. `/usr/local/etc/narratio/pipeline.yml` 2. `/etc/narratio/pipeline.yml` ### `campaign.yml` Selection rules: - if `--campaign-file` is set, use that path; - else if `--campaign ` is set, use `{pipeline.campaigns.root}/{id}/campaign.yml`; - else use `{pipeline.campaigns.root}/{pipeline.campaigns.default_campaign_id}/campaign.yml`. ### `session.yml` When `--session` is omitted, local search order is: 1. `/usr/local/etc/narratio/session.yml` 2. `/etc/narratio/session.yml` If local session discovery fails and a `session_id` is known, Narratio attempts remote session loading from: - `{root_prefix}/campaigns/{campaign}/sessions/{session_id}/session.yml` using configured object storage. ## Validation and Merge Rules - YAML decode is strict (`KnownFields(true)`): unknown fields fail load. - Session files must be concrete; unresolved `{{ ... }}` placeholders fail load. - Pipeline defaults are applied before validation. - Campaign and session identities must agree. - Stable files (`speakers_file`, `autocorrect_file`, `glossary_file`, `players_file`, `party_file`) resolve from session overrides when provided, otherwise from campaign defaults. - Exactly one audio mode must be configured in session input: - local (`audio_dir` or `audio_files`), or - S3 (`audio_s3.prefix`). ## Minimal Working Configuration `pipeline.yml` ```yaml campaigns: root: /usr/local/share/narratio/campaigns default_campaign_id: sample-campaign whisperx: transcribe_url: https://transcription.example.com/transcribe ``` `campaign.yml` ```yaml campaign_id: sample-campaign inputs: speakers_file: ./speakers.yml autocorrect_file: ./autocorrect.yml glossary_file: ./glossary.yml players_file: ./players.yml party_file: ./party.yml ``` `session.yml` (local audio) ```yaml session_id: 2026-05-03 inputs: audio_dir: ./audio ``` ## Secrets Handling - Do not place raw secrets in YAML. - Use env var names in config (for example `pipeline.audita.llm_api_key_env`). - Optionally load env files from `pipeline.secrets.env_dir`. - Commands that need storage/auth load filesystem secrets before constructing adapters. ## Publish Configuration Summary Publish rules live under `pipeline.publish`. ```yaml publish: enabled: true upload_run: true outputs: - source: narratio.transcript.final_trimmed dest: transcripts/final.trimmed.json required: true - source: narratio.transcript.final_markdown dest: transcripts/final.md required: true - source: narratio.transcript.final_trimmed_markdown dest: transcripts/final.trimmed.md required: true - source: narratio.artifact.session_recap dest: artifacts/session_recap.md required: true locks: - source: narratio.artifact.session_recap reason: manual post-publish edits ``` Rules: - `outputs[].source` is required. - `outputs[].dest` may be omitted when derivable from source. - `outputs[].required` defaults to `true`. - static locks (`pipeline.publish.locks`) merge with remote locks (`{session_prefix}/locks.yml`), with static locks taking precedence on duplicates. ## Full Schema ### Pipeline | Field | Type | Required | Default / Rule | | --- | --- | --- | --- | | `pipeline.workspace.root` | string | No | `/var/lib/narratio` | | `pipeline.workspace.cleanup_after_publish` | bool | No | `false` | | `pipeline.campaigns.root` | string | No | `/usr/local/share/narratio/campaigns` | | `pipeline.campaigns.default_campaign_id` | string | No | empty | | `pipeline.secrets.env_dir` | string | No | empty | | `pipeline.storage.backend` | string | No | empty | | `pipeline.storage.s3.bucket` | string | Conditional | required for S3 session-audio and for publish upload when backend is `s3` | | `pipeline.storage.s3.root_prefix` | string | No | `dnd` | | `pipeline.storage.s3.region` | string | No | empty | | `pipeline.storage.s3.endpoint` | string | No | empty | | `pipeline.storage.s3.force_path_style` | bool | No | `false` | | `pipeline.storage.s3.access_key_id_env` | string | No | `OBJECT_STORAGE_KEY_ID` | | `pipeline.storage.s3.secret_access_key_env` | string | No | `OBJECT_STORAGE_KEY` | | `pipeline.spool.root` | string | No | `/var/spool/narratio` | | `pipeline.spool.delete_audio_after_publish` | bool | No | `false` | | `pipeline.cache.root` | string | No | `/var/cache/narratio` | | `pipeline.cache.s3_audio` | bool | No | `true` | | `pipeline.publish.enabled` | bool | No | `true` | | `pipeline.publish.upload_run` | bool | No | `true` | | `pipeline.publish.outputs[]` | list | No | defaults to final trimmed JSON plus final and final-trimmed Markdown outputs | | `pipeline.publish.outputs[].source` | string | Yes (per rule) | must reference built-in or configured artifact source | | `pipeline.publish.outputs[].dest` | string | Conditional | derived if omitted and source supports derivation | | `pipeline.publish.outputs[].required` | bool | No | `true` | | `pipeline.publish.locks[]` | list | No | empty | | `pipeline.publish.locks[].source` | string | Yes (per lock) | must reference supported publish source | | `pipeline.publish.locks[].reason` | string | No | empty | | `pipeline.whisperx.transcribe_url` | string | Yes | valid URL | | `pipeline.whisperx.language` | string | No | `en` | | `pipeline.whisperx.timeout` | duration | No | `30m` | | `pipeline.whisperx.retries` | int | No | `3` | | `pipeline.whisperx.retry_delay` | duration | No | `2s` | | `pipeline.whisperx.concurrency` | int | No | `2` | | `pipeline.seriatim.binary` | string | No | `seriatim` | | `pipeline.seriatim.timeout` | duration | No | `10m` | | `pipeline.seriatim.output_schema` | string | No | `seriatim-intermediate` | | `pipeline.seriatim.coalesce_gap` | float | No | `3.0` | | `pipeline.seriatim.report` | bool | No | `true` | | `pipeline.seriatim.env.overlap_word_run_gap` | float | No | unset | | `pipeline.seriatim.env.overlap_word_run_reorder_window` | float | No | unset | | `pipeline.seriatim.env.backchannel_max_duration` | float | No | unset | | `pipeline.seriatim.env.filler_max_duration` | float | No | unset | | `pipeline.audita.binary` | string | No | `audita` | | `pipeline.audita.timeout` | duration | No | `3h` | | `pipeline.audita.llm_api_key_env` | string | No | empty | | `pipeline.audita.modules[]` | list[string] | No | empty | | `pipeline.audita.base_url` | string | No | empty | | `pipeline.audita.model` | string | No | empty | | `pipeline.audita.total_llm_concurrency` | int | No | unset | | `pipeline.audita.proposal_llm_concurrency` | int | No | unset | | `pipeline.audita.validation_model` | string | No | empty | | `pipeline.audita.validation_llm_concurrency` | int | No | unset | | `pipeline.audita.transcript_description` | string | No | empty | | `pipeline.audita.config_path` | string | No | empty | | `pipeline.audita.output_schema` | string | No | empty | | `pipeline.audita.work_dir_retention` | string | No | empty | | `pipeline.audita.report` | bool | No | `true` | | `pipeline.normalize.output_path` | string | No | `transcripts/final.json` | | `pipeline.normalize.output_schema` | string | No | `seriatim-intermediate` | | `pipeline.normalize.report` | bool | No | `true` | | `pipeline.trim.enabled` | bool | No | `true` | | `pipeline.trim.output_path` | string | No | `transcripts/final.trimmed.json` | | `pipeline.trim.bounds.prompt_id` | string | No | `dnd.session_bounds` | | `pipeline.trim.bounds.profile_id` | string | No | empty | | `pipeline.trim.bounds.transcript_input_name` | string | No | `transcript` | | `pipeline.trim.bounds.output_path` | string | No | `artifacts/session_bounds.json` | | `pipeline.trim.bounds.timeout` | duration | No | `10m` | | `pipeline.trim.bounds.render_debug` | bool | No | `false` | | `pipeline.trim.bounds.render_output_path` | string | Conditional | required when `render_debug` is true | | `pipeline.trim.seriatim.report` | bool | No | `false` | | `pipeline.render.enabled` | bool | No | `true` | | `pipeline.render.format` | string | No | `markdown` (only supported value) | | `pipeline.render.title` | string | No | empty (falls back to `session.title` when set) | | `pipeline.render.include_timestamps` | bool | No | `true` | | `pipeline.render.include_segment_ids` | bool | No | `true` | | `pipeline.render.include_metadata` | bool | No | `false` | | `pipeline.scriptorium.binary` | string | No | `scriptorium` | | `pipeline.scriptorium.config_path` | string | No | empty | | `pipeline.scriptorium.timeout` | duration | No | `10m` | | `pipeline.scriptorium.render_debug` | bool | No | `false` | | `pipeline.scriptorium.artifacts` | map | No | empty | | `pipeline.notification.backend` | string | No | empty | | `pipeline.notification.recipient` | string | No | empty | | `pipeline.notification.timeout` | duration | No | `30s` | ### Scriptorium Artifact Entries For each `pipeline.scriptorium.artifacts.`: | Field | Type | Required | Rule | | --- | --- | --- | --- | | `enabled` | bool | No | `false` if omitted | | `depends_on[]` | list[string] | No | must reference configured artifact keys; no self-reference; enabled graph must be acyclic | | `render_debug` | bool | No | per-artifact override | | `prompt_id` | string | Conditional | required when artifact is enabled | | `profile_id` | string | No | empty | | `output_path` | string | Conditional | required when enabled; also required when referenced by publish/output/input rules | | `timeout` | duration | No | artifact override | | `inputs` | map | No | input key names must be non-empty | | `vars` | map | No | values must be string or bool; `session_id` is reserved and overwritten by Narratio | Narratio adds `session_id=narratio-session-` to every Scriptorium request for sticky upstream LLM routing. If an artifact config sets `vars.session_id`, Narratio replaces that value before invoking Scriptorium. Use a different variable name if a prompt needs the raw Narratio session ID as content. For each artifact input `pipeline.scriptorium.artifacts..inputs.`: | Field | Type | Required | Rule | | --- | --- | --- | --- | | `source` | string | Yes | built-in runtime source, prepared input source, `narratio.artifact.`, or `narratio.previous_session.artifact.` | | `artifact` | string | No | optional passthrough adapter field | | `path` | string | No | optional passthrough adapter field | | `required` | bool | No | optional input requirement | ### Campaign | Field | Type | Required | Notes | | --- | --- | --- | --- | | `campaign_id` | string | Yes | canonical campaign identity | | `session_template_file` | string | No | used by `session init` when set | | `inputs.speakers_file` | string | Yes | stable input default | | `inputs.autocorrect_file` | string | Yes | stable input default | | `inputs.glossary_file` | string | Yes | stable input default | | `inputs.players_file` | string | Yes | stable input default | | `inputs.party_file` | string | Yes | stable input default | ### Session | Field | Type | Required in session file | Notes | | --- | --- | --- | --- | | `session_id` | string | Yes | must match CLI session target when provided | | `previous_session_id` | string | No | must not equal `session_id` | | `campaign` | string | No | filled from `campaign_id` during resolve if omitted | | `date` | string | No | metadata | | `title` | string | No | metadata | | `inputs.speakers_file` | string | No | overrides campaign stable input | | `inputs.autocorrect_file` | string | No | overrides campaign stable input | | `inputs.glossary_file` | string | No | overrides campaign stable input | | `inputs.players_file` | string | No | overrides campaign stable input | | `inputs.party_file` | string | No | overrides campaign stable input | | `inputs.audio_dir` | string | Conditional | local audio mode | | `inputs.audio_files[]` | list[string] | Conditional | local audio mode | | `inputs.audio_s3.prefix` | string | Conditional | S3 audio mode | Audio rules: - configure local mode (`audio_dir` or `audio_files`) or S3 mode (`audio_s3.prefix`), not both. ## Maintained Examples - `examples/pipeline.minimal.yml` - `examples/pipeline.production.yml` - `examples/pipeline.full.annotated.yml` - `examples/campaigns/sample-campaign/campaign.yml` - `examples/session.local-audio.yml` - `examples/session.s3-audio.yml` - `examples/session.template.yml`