# 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. The downloaded remote session file is command-scoped: Narratio removes it after the command finishes and records only the remote object provenance alongside the durable copied session input. ### Identity segments Campaign IDs (`campaign_id` and `default_campaign_id`), session IDs, previous session IDs, and Narratio run IDs are opaque portable segments. They must use only ASCII letters, digits, `.`, `_`, and `-`; empty values, `.`/`..`, path separators, drive forms, whitespace, control characters, and non-ASCII text are rejected. Narratio does not trim or rewrite these values. Existing manifests or remote state with an unsafe legacy identity must be migrated before use. ## Validation and Merge Rules - YAML decode is strict (`KnownFields(true)`) and accepts exactly one document: unknown fields or trailing documents fail load. - Configured timeout and retry-delay durations must be positive. An omitted artifact timeout continues to inherit its configured Scriptorium timeout. - 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 credential files from `pipeline.secrets.env_dir`. Each valid environment-variable filename supplies one value; trailing CR/LF is removed. - An existing process environment value takes precedence over a credential file. - Credential directories and files must not be symlinks and must be regular, bounded files (at most 8 KiB per value). On POSIX, provision the directory with no group/other access (normally `0700`) and files with no group/other access (normally `0600`). - 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. - extraction sources require an explicit `outputs[].dest` and publish only when a rule names that source; the Notarius index and complete bundle are not publish sources. - `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 | `local`; supported values are `local` and `s3` (case-insensitive) | | `pipeline.storage.s3.bucket` | string | Conditional | required when backend is `s3` and S3 session-audio or publish upload is enabled | | `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 | absolute `http` or `https` 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.notarius.enabled` | bool | No | `false` | | `pipeline.notarius.binary` | string | No | `notarius` | | `pipeline.notarius.config_path` | string | Conditional | required when enabled; relative paths resolve from the pipeline file directory | | `pipeline.notarius.pipeline_id` | string | Conditional | required when enabled | | `pipeline.notarius.timeout` | duration | No | `3h`; must be positive | | `pipeline.notarius.working_directory` | string | No | directory containing resolved `config_path`; relative paths resolve from the pipeline file directory | | `pipeline.notarius.outputs` | map | Conditional | at least one entry when enabled | | `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.mode` | string | No | `noop`; the only supported notification mode until a provider is implemented | ### Notarius Output Entries For each `pipeline.notarius.outputs.`: | Field | Type | Required | Rule | | --- | --- | --- | --- | | `lane_id` | string | Yes | unique Notarius lane ID | | `media_type` | string | Yes | exact accepted descriptor media type | | `schema_id` | string | Yes | exact accepted descriptor schema ID | | `schema_version` | string | Yes | exact accepted descriptor schema version | | `module_key` | string | No | exact accepted module key when set | Output names must match `^[a-z][a-z0-9_]*$` and become selectable sources named `narratio.extraction.`. Lane IDs must be unique. Every declared output is required from a successful Notarius result; a missing, rejected, duplicate, or contract-incompatible lane fails extraction. See the [complete maintained example](../examples/pipeline.full.annotated.yml) for the current ten-lane D&D mapping and the [Notarius contract](./integrations/notarius.md) for compatibility ownership. ### 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. Without `--artifacts`, analyze executes enabled configured artifacts. With an explicit `--artifacts` list, the exact named configured artifacts are the one-invocation execution set even if their `enabled` values are false; the list does not automatically include dependencies. Named artifacts must therefore be configured with valid executable fields, and their configured dependencies must already be available to analyze. This override affects analyze planning only; publish uses the list only to filter configured `narratio.artifact.` output rules. For each artifact input `pipeline.scriptorium.artifacts..inputs.`: | Field | Type | Required | Rule | | --- | --- | --- | --- | | `source` | string | Yes | built-in runtime source, prepared input source, `narratio.extraction.`, `narratio.artifact.`, or `narratio.previous_session.artifact.` | | `required` | bool | No | optional input requirement | `artifact` and `path` are obsolete and rejected by strict configuration loading. Use the canonical `source` identifier to select the input; Narratio does not provide adapter-specific input passthrough fields. ### Notifications Narratio currently supports only `notification.mode: noop`, which is also the default when the section is omitted. The notify stage performs no delivery in this mode. Backend, recipient, timeout, and other provider settings are rejected by strict configuration loading until Narratio has a provider integration. ### Campaign | Field | Type | Required | Notes | | --- | --- | --- | --- | | `campaign_id` | string | Yes | canonical opaque 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 | opaque identity; must match CLI session target when provided | | `previous_session_id` | string | No | opaque identity; must not equal `session_id` | | `campaign` | string | No | opaque identity; 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. - `audio_s3` requires `pipeline.storage.backend: s3` and a configured S3 bucket. ### Storage backend selection `local` is the default and disables remote object-store operations. Configure `s3` explicitly before supplying `storage.s3`; a populated S3 block does not select a backend on its own. Unknown backend names and an S3 block paired with `local` are rejected during configuration validation. ### Previous-session expectation `previous_session_id` is optional in a session file. When a command supplies `--previous-session-id`, however, the session file must contain the same value; an omitted or different value is rejected before the command performs work. ## Maintained Examples See the [maintained examples index](../examples/README.md) for complete pipeline, campaign, session, template, and input fixtures. Keep complete copyable files there rather than duplicating them in this reference.