19 KiB
Configuration
1. Overview
Narratio loads three YAML files:
pipeline.yml: pipeline-level runtime configuration.campaign.yml: stable campaign identity and campaign-level input defaults.session.yml: per-session metadata and input selection, loaded locally or from the configured S3 backend.
These commands load and validate all three files before running:
narratio runnarratio plannarratio resumenarratio run-stagenarratio restore
Behavior:
- strict YAML decode is enabled (
KnownFields(true)): unknown fields fail. - session templates render before session YAML decode.
- remote
session.ymluses the same strict decode and template behavior as localsession.yml. - defaults are applied for optional pipeline fields.
- campaign-level stable input paths fill missing session input paths.
- session-level stable input paths override campaign-level input paths.
- validation enforces required fields, value formats, and cross-field constraints.
2. Config file discovery
These commands use the same config discovery behavior:
narratio runnarratio plannarratio resumenarratio run-stagenarratio restore
Pipeline config lookup:
- if
--config <path>is provided, that path is used. - if omitted, Narratio searches in order:
/usr/local/etc/narratio/pipeline.yml/etc/narratio/pipeline.yml
- first existing file wins.
Campaign config lookup:
- if
--campaign <path>is provided, that path is used. - if omitted, Narratio searches in order:
/usr/local/etc/narratio/campaign.yml/etc/narratio/campaign.yml
- first existing file wins.
Session config lookup:
- if
--session <path>is provided, that path is used. - if
--sessionis omitted, Narratio searches locally in order:/usr/local/etc/narratio/session.yml/etc/narratio/session.yml
- first existing local file wins.
- if no local session file is found,
--session-id <value>is present, storage is configured, and campaign identity is resolved, Narratio loads remotesession.ymlfrom:{root_prefix}/campaigns/{campaign}/sessions/{session_id}/session.yml
- local discovery always runs before remote fallback.
- local files in the current working directory are used only when passed explicitly, for example
--config ./pipeline.yml --campaign ./campaign.yml --session ./session.yml.
3. Session templating
Template behavior for local and remote session.yml:
- supported placeholders:
{{session_id}}{{ session_id }}{{previous_session_id}}{{ previous_session_id }}
--session-id <value>supplies the placeholder value.--previous-session-id <value>supplies the previous-session placeholder value.- unresolved placeholders fail load.
- if rendered
session_idmismatches--session-id, load fails. - if rendered
previous_session_idmismatches--previous-session-id, load fails.
4. Minimal config set
pipeline.yml
whisperx:
transcribe_url: "https://transcription.example.com/transcribe"
Why this is sufficient:
whisperx.transcribe_urlis required.workspace.rootdefaults to/var/lib/narratio.- optional sections (
seriatim,audita,archive,scriptorium,trim,normalize, etc.) receive defaults or stay inactive.
campaign.yml
campaign: sample-campaign
inputs:
speakers_file: ./speakers.yml
autocorrect_file: ./autocorrect.yml
glossary_file: ./glossary.yml
Why this is sufficient:
campaignsupplies the stable campaign identity.- stable input files are required and resolve relative to
campaign.ymlwhen copied duringprepare.
session.yml
session_id: "{{ session_id }}"
inputs:
audio_dir: ./audio
Why this is sufficient:
session_idis required and can be rendered from--session-id.campaigncan be omitted because it is supplied bycampaign.yml.- stable input paths can be omitted because
campaign.ymlsupplies defaults. - local
audio_dirresolves relative tosession.yml.
Minimal local-file usage:
narratio run --config /path/to/pipeline.yml --campaign ./campaign.yml --session ./session.yml --session-id 2026-05-03
Previous-session-enabled variant:
session_id: "{{ session_id }}"
previous_session_id: "{{ previous_session_id }}"
inputs:
audio_dir: ./audio
narratio run --config /path/to/pipeline.yml --campaign ./campaign.yml --session ./session.yml --session-id 2026-05-03 --previous-session-id 2026-04-26
5. Production-oriented config set
pipeline.yml
workspace:
root: /var/lib/narratio/workspace
cleanup_after_archive: true
storage:
backend: s3
s3:
bucket: my-dnd-archive
root_prefix: dnd
region: us-east-1
access_key_id_env: OBJECT_STORAGE_KEY_ID
secret_access_key_env: OBJECT_STORAGE_KEY
spool:
root: /var/spool/narratio
delete_audio_after_archive: true
cache:
root: /var/cache/narratio
s3_audio: true
archive:
enabled: true
upload_run: true
promote_artifacts:
- source: narratio.transcript.trimmed
dest: transcripts/trimmed.json
required: true
- source: narratio.artifact.session_recap
dest: artifacts/session_recap.md
required: true
locks:
- source: narratio.artifact.session_recap
reason: Final recap was manually edited.
whisperx:
transcribe_url: "https://transcription.example.com/transcribe"
scriptorium:
artifacts:
session_recap:
enabled: true
prompt_id: dnd.session_recap
output_path: artifacts/session_recap.md
inputs:
transcript:
source: narratio.transcript.trimmed
required: true
previous_recap:
source: narratio.previous_session.artifact.session_recap
required: false
campaign.yml
campaign: forsaken
inputs:
speakers_file: /srv/narratio/campaigns/forsaken/speakers.yml
autocorrect_file: /srv/narratio/campaigns/forsaken/autocorrect.yml
glossary_file: /srv/narratio/campaigns/forsaken/glossary.yml
Local session.yml
session_id: "{{ session_id }}"
previous_session_id: "{{ previous_session_id }}"
date: 2026-05-03
title: The Black Cabin
inputs:
audio_s3:
prefix: audio/
S3-first session config
For S3-first operation, upload the same session.yml content to:
{root_prefix}/campaigns/{campaign}/sessions/{session_id}/session.yml
Then run with explicit or discovered pipeline/campaign config and no --session:
narratio run --config /usr/local/etc/narratio/pipeline.yml --campaign /usr/local/etc/narratio/campaign.yml --session-id 2026-05-03 --previous-session-id 2026-04-26
Operational notes:
- archive promotion is explicit and source-based via
archive.promote_artifacts. sourceis required;destis optional and derived when omitted.archive.locksskips top-level promotion overwrites for static locked sources while preserving run-local uploads.- operator-created mutable locks are stored at
{root_prefix}/campaigns/{campaign}/sessions/{session_id}/locks.ymland are merged with static locks. - Narratio does not auto-promote all generated analyze artifacts.
restorereads the same config/campaign/session inputs and restore scope is bounded by committed archive current state.cleanremoves workspace/spool state by default and preservespipeline.cache.rootunless--clear-cacheis passed.
6. Full pipeline reference
| Path | Type | Required | Default |
|---|---|---|---|
pipeline.workspace.root |
string | No | /var/lib/narratio |
pipeline.workspace.cleanup_after_archive |
bool | No | false |
pipeline.secrets.env_dir |
string | Conditional | none |
pipeline.storage.backend |
string | No | empty |
pipeline.storage.bucket |
string | No | empty |
pipeline.storage.prefix |
string | No | empty |
pipeline.storage.s3.bucket |
string | Conditional | empty |
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_archive |
bool | No | false |
pipeline.cache.root |
string | No | /var/cache/narratio |
pipeline.cache.s3_audio |
bool | No | true |
pipeline.archive.enabled |
bool | No | true |
pipeline.archive.upload_run |
bool | No | true |
pipeline.archive.promote_artifacts[] |
list | No | trimmed transcript rule |
pipeline.archive.promote_artifacts[].source |
string | Yes (per rule) | none |
pipeline.archive.promote_artifacts[].dest |
string | No | derived from source |
pipeline.archive.promote_artifacts[].required |
bool | No | true |
pipeline.archive.locks[] |
list | No | empty |
pipeline.archive.locks[].source |
string | Yes (per lock) | none |
pipeline.archive.locks[].reason |
string | No | empty |
pipeline.whisperx.transcribe_url |
string | Yes | none |
pipeline.whisperx.language |
string | No | en |
pipeline.whisperx.timeout |
duration string | No | 30m |
pipeline.whisperx.retries |
int | No | 3 |
pipeline.whisperx.retry_delay |
duration string | No | 2s |
pipeline.whisperx.concurrency |
int | No | 2 |
pipeline.seriatim.binary |
string | No | seriatim |
pipeline.seriatim.timeout |
duration string | 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 string | 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/normalized.json |
pipeline.normalize.output_schema |
string | No | seriatim-intermediate |
pipeline.normalize.report |
bool | No | true |
pipeline.trim.enabled |
bool | No | false |
pipeline.trim.output_path |
string | Conditional | none |
pipeline.trim.bounds.prompt_id |
string | Conditional | none |
pipeline.trim.bounds.profile_id |
string | No | empty |
pipeline.trim.bounds.transcript_input_name |
string | Conditional | none |
pipeline.trim.bounds.output_path |
string | Conditional | none |
pipeline.trim.bounds.timeout |
duration string | No | 10m |
pipeline.trim.bounds.render_debug |
bool | No | false |
pipeline.trim.bounds.render_output_path |
string | Conditional | none |
pipeline.trim.seriatim.report |
bool | No | false |
pipeline.scriptorium.binary |
string | No | scriptorium |
pipeline.scriptorium.config_path |
string | No | empty |
pipeline.scriptorium.timeout |
duration string | No | 10m |
pipeline.scriptorium.render_debug |
bool | No | false |
pipeline.scriptorium.artifacts |
map | No | empty |
pipeline.scriptorium.artifacts.<name>.enabled |
bool | No | false |
pipeline.scriptorium.artifacts.<name>.depends_on[] |
list[string] | No | empty |
pipeline.scriptorium.artifacts.<name>.render_debug |
bool | No | unset |
pipeline.scriptorium.artifacts.<name>.prompt_id |
string | Conditional | none |
pipeline.scriptorium.artifacts.<name>.profile_id |
string | No | empty |
pipeline.scriptorium.artifacts.<name>.output_path |
string | Conditional | none |
pipeline.scriptorium.artifacts.<name>.timeout |
duration string | No | empty |
pipeline.scriptorium.artifacts.<name>.inputs.<key>.source |
string | Conditional | none |
pipeline.scriptorium.artifacts.<name>.inputs.<key>.artifact |
string | No | empty |
pipeline.scriptorium.artifacts.<name>.inputs.<key>.path |
string | No | empty |
pipeline.scriptorium.artifacts.<name>.inputs.<key>.required |
bool | No | false |
pipeline.scriptorium.artifacts.<name>.vars.<key> |
map value | No | empty |
pipeline.analyzer.binary_path |
string | No | empty |
pipeline.analyzer.timeout |
duration string | No | empty |
pipeline.analyzer.artifacts.output_dir |
string | No | empty |
pipeline.analyzer.artifacts.types[] |
list[string] | No | empty |
pipeline.notification.backend |
string | No | empty |
pipeline.notification.recipient |
string | No | empty |
pipeline.notification.timeout |
duration string | No | empty |
Scriptorium artifact-key and dependency rules:
- artifact keys must match
^[a-z][a-z0-9_]*$. - enabled artifacts require
prompt_idandoutput_path. output_pathmust be relative, traversal-safe, and underartifacts/.- configured artifact input sources use
narratio.artifact.<name>. - if input source references
narratio.artifact.<name>, artifact<name>must exist and must be listed independs_on. - every
depends_onentry must be a configured artifact key. - self-dependency is rejected.
- enabled dependency cycles are rejected.
- any artifact referenced by
depends_onornarratio.artifact.<name>source must defineoutput_path(even if not enabled).
Allowed pipeline.scriptorium.artifacts.<name>.inputs.<key>.source values:
narratio.previous_session.artifact.<configured_artifact_key>narratio.transcript.mergednarratio.transcript.polishednarratio.transcript.fullnarratio.transcript.trimmednarratio.bounds.sessionnarratio.artifact.<configured_artifact_key>previous_session_artifact(legacy path-based source; usesinputs.<key>.path)
pipeline.archive.promote_artifacts[].source values:
narratio.transcript.mergednarratio.transcript.polishednarratio.transcript.fullnarratio.transcript.trimmednarratio.bounds.sessionnarratio.artifact.<configured_artifact_key>
pipeline.archive.locks[].source accepts the same source values as pipeline.archive.promote_artifacts[].source.
Archive promotion destination rules:
destmust be a clean relative path (not absolute, no traversal).- duplicate
destvalues are rejected. - if
destis omitted:- built-in sources derive their canonical destination path;
- configured sources derive from
pipeline.scriptorium.artifacts.<name>.output_path; - derivation failure is a config validation error.
Archive lock rules:
- locks are source-based and do not accept
dest. - duplicate lock sources are rejected.
- static
pipeline.archive.lockswin over remote mutable locks for the same source. - locked promotions are recorded as intentional skips in archive metadata.
- locked required promotions do not fail archive by default.
- ordinary
--forcereruns do not override locks.
Remote mutable lock store:
- path:
{root_prefix}/campaigns/{campaign}/sessions/{session_id}/locks.yml. - strict YAML shape: top-level
locks, each withsourceand optionalreason. narratio locks addandnarratio locks removemutate only the remote lock store.- writes use existence checks plus
--forcefor updates; they are not compare-and-swap atomic.
Restore-related implications:
- restore remote identity requires archive S3 identity to resolve (
pipeline.storage.s3.bucketand session prefix derivation inputs). - restore scope considers committed current state and durable paths (
manifest.json,transcripts/**,artifacts/**,previous/**, optionalaudio/**). - S3 audio downloads use
pipeline.spool.rootfor active downloads andpipeline.cache.rootfor reusable cached audio whenpipeline.cache.s3_audiois true. pipeline.cache.rootis durable local cache state. It is not workspace state and is preserved by default bynarratio clean.
7. Full campaign reference
| Path | Type | Required | Default |
|---|---|---|---|
campaign.campaign |
string | Yes | none |
campaign.inputs.speakers_file |
string | Yes | none |
campaign.inputs.autocorrect_file |
string | Yes | none |
campaign.inputs.glossary_file |
string | Yes | none |
Campaign input paths may be absolute or relative. Relative paths resolve from the directory containing campaign.yml.
8. Full session reference
| Path | Type | Required | Default |
|---|---|---|---|
session.session_id |
string | Yes | none |
session.previous_session_id |
string | No | empty |
session.campaign |
string | No | campaign.campaign |
session.date |
string | No | empty |
session.title |
string | No | empty |
session.inputs.audio_dir |
string | Conditional | empty |
session.inputs.audio_files[] |
list[string] | Conditional | empty |
session.inputs.audio_s3.prefix |
string | Conditional | none |
session.inputs.speakers_file |
string | No | campaign.inputs.speakers_file |
session.inputs.autocorrect_file |
string | No | campaign.inputs.autocorrect_file |
session.inputs.glossary_file |
string | No | campaign.inputs.glossary_file |
Session input paths may be absolute or relative. Relative audio paths and session-level stable input overrides resolve from the directory containing session.yml. If both campaign.yml and session.yml specify campaign identity, the values must match.
Audio-source rule:
- configure exactly one mode:
audio_dir, oraudio_files(at least one), oraudio_s3.prefix
audio_s3cannot be combined with local audio fields.
Previous-session rule:
- if
session.previous_session_idis set, it must not equalsession.session_id. - canonical previous-session sources (
narratio.previous_session.artifact.<name>) are hydrated duringpreparefrom archive current state when required by enabled configured artifacts.
9. Secrets
Narratio supports filesystem-based secret injection via pipeline.secrets.env_dir.
Behavior:
env_dirmay be absolute or relative.- relative
env_dirresolves from current working directory. - files with valid env-var names (
[A-Za-z_][A-Za-z0-9_]*) are loaded. - values are loaded from file contents with trailing newline trimming.
- existing process env vars are preserved.
- invalid names and subdirectories are skipped.
- missing/unreadable
env_dirfails command execution.
Guidance:
- do not put secret values directly in YAML.
- configure env var names in config and provide values via env/secrets files.
10. Examples
Maintained examples:
examples/pipeline.minimal.ymlexamples/pipeline.production.ymlexamples/pipeline.full.annotated.ymlexamples/campaign.ymlexamples/session.template.ymlexamples/session.local-audio.ymlexamples/session.s3-audio.yml
These examples are validated by internal/config tests.