317 lines
13 KiB
Markdown
317 lines
13 KiB
Markdown
# Narratio Architecture
|
|
|
|
## 1. Purpose
|
|
|
|
`narratio` is a Go orchestrator for D&D session processing. It runs a stage-based local pipeline from audio input through transcript processing and artifact generation, with manifest-based skip/force/resume behavior.
|
|
|
|
Narratio integrates with Scriptorium through the **public CLI** (`scriptorium run` and `scriptorium render`) via synchronous subprocess execution.
|
|
|
|
## 2. Current Status
|
|
|
|
Implemented:
|
|
|
|
- strict `pipeline.yml` + `session.yml` loading with strict YAML field checking (`KnownFields(true)`)
|
|
- local workspace/session layout, lock file handling, artifact path helpers, checksums, and atomic writes
|
|
- manifest store and stage status transitions for resumable runs
|
|
- real `prepare`, `transcribe`, `merge`, and `polish` stages
|
|
- real WhisperX HTTP adapter
|
|
- real Seriatim subprocess adapter
|
|
- real Audita subprocess adapter
|
|
- real Scriptorium subprocess adapter
|
|
- real `normalize` stage producing `transcripts/normalized.json`
|
|
- real `trim` stage producing `transcripts/trimmed.json`
|
|
- real `analyze` stage for initial `session_recap` generation
|
|
- optional Scriptorium render diagnostics (`render_debug`) before production run
|
|
|
|
Still placeholder/future:
|
|
|
|
- `archive` stage behavior
|
|
- `notify` stage behavior
|
|
- additional Scriptorium artifact types beyond `session_recap`
|
|
- artifact-to-artifact workflows beyond the initial single-artifact implementation
|
|
- generic stale detection based on input/config checksums
|
|
|
|
## 3. Pipeline and Stage Boundaries
|
|
|
|
Canonical stage order:
|
|
|
|
1. `prepare`
|
|
2. `transcribe`
|
|
3. `merge`
|
|
4. `polish`
|
|
5. `normalize`
|
|
6. `trim`
|
|
7. `analyze`
|
|
8. `archive`
|
|
9. `notify`
|
|
|
|
Boundary rules:
|
|
|
|
- orchestration logic lives in `internal/app`
|
|
- stage business logic lives in `internal/stage`
|
|
- external-tool CLI construction lives in adapter packages
|
|
- Scriptorium CLI details stay in `internal/adapters/scriptorium`
|
|
|
|
## 4. Scriptorium Integration Model
|
|
|
|
Integration mode:
|
|
|
|
- public CLI subprocesses only (no Scriptorium internal Go packages, no HTTP API)
|
|
- production generation uses `scriptorium run`
|
|
- diagnostics/testing render uses `scriptorium render --format json`
|
|
|
|
Run invocation shape used by adapter:
|
|
|
|
```bash
|
|
scriptorium run --prompt <prompt_id> --input name=path --out <output_path>
|
|
```
|
|
|
|
Optional flags passed when configured:
|
|
|
|
- `--config <path>`
|
|
- `--profile <profile_id>`
|
|
- repeated `--var name=value`
|
|
- repeated `--input name=path`
|
|
- `--timeout <duration>`
|
|
- `--api-key-env <ENV_NAME>` when configured
|
|
|
|
Render invocation shape used by adapter:
|
|
|
|
```bash
|
|
scriptorium render --prompt <prompt_id> --input name=path --format json --out <render_output_path>
|
|
```
|
|
|
|
Adapter behavior:
|
|
|
|
- always passes `--out`
|
|
- captures stdout/stderr separately
|
|
- writes generated invocation metadata YAML (redacted, no secrets)
|
|
- treats exit code `0` as success
|
|
- treats exit code `1` as failure
|
|
- treats exit code `2` as failure with `validation_failed=true` and preserves output metadata when available
|
|
- validates successful output files exist and are non-empty
|
|
- does not treat non-empty stderr as failure by itself
|
|
|
|
## 5. Configuration Contract
|
|
|
|
`pipeline.scriptorium` is optional. Existing pipelines without Scriptorium continue to work.
|
|
|
|
`pipeline.trim` is optional. Existing pipelines without trim config continue to work.
|
|
|
|
`pipeline.normalize` is optional. Existing pipelines without normalize config continue to work.
|
|
|
|
When `pipeline.normalize` is omitted, defaults are applied:
|
|
|
|
- `output_path: transcripts/normalized.json`
|
|
- `output_schema: seriatim-intermediate`
|
|
- `report: true`
|
|
|
|
When `pipeline.normalize` is present:
|
|
|
|
- `output_path` must be non-empty
|
|
- `output_schema` must be one of `seriatim-minimal`, `seriatim-intermediate`, or `seriatim-full`
|
|
- relative `output_path` values are session-workdir-relative paths
|
|
- Seriatim binary settings still come from `pipeline.seriatim`
|
|
|
|
When `pipeline.trim` is present:
|
|
|
|
- `enabled` is optional and defaults to `false` when omitted
|
|
- relative `output_path`, `bounds.output_path`, and `bounds.render_output_path` values are session-workdir-relative paths
|
|
- do not store secrets in trim config values
|
|
|
|
When `pipeline.trim.enabled: true`:
|
|
|
|
- `output_path` is required and non-empty
|
|
- `bounds.prompt_id` is required and non-empty
|
|
- `bounds.transcript_input_name` is required and non-empty
|
|
- `bounds.output_path` is required and non-empty
|
|
- `bounds.timeout` must parse as a Go duration when provided
|
|
- `bounds.render_debug: true` requires non-empty `bounds.render_output_path`
|
|
- `bounds.profile_id` may be empty to use the prompt default profile
|
|
- prompt IDs are config values, not hardcoded stage logic
|
|
|
|
When `pipeline.scriptorium` is present:
|
|
|
|
- `binary` is required and non-empty
|
|
- `config_path` is optional; when provided it must be non-empty
|
|
- `timeout` is optional; when provided it must parse as a Go duration
|
|
- default `timeout` is `10m`
|
|
- unknown YAML fields fail strict decode
|
|
|
|
Artifacts are configured as a map under `pipeline.scriptorium.artifacts` so multiple artifacts are possible in the config shape.
|
|
|
|
For each artifact definition:
|
|
|
|
- `enabled: true` requires non-empty `prompt_id`
|
|
- `enabled: true` requires non-empty `output_path`
|
|
- `timeout` must parse as Go duration when present
|
|
- optional per-artifact `render_debug` may override global `scriptorium.render_debug`
|
|
- `inputs` are named and each input requires non-empty `source`
|
|
- inputs may be optional (`required: false`)
|
|
- `vars` values currently support `string` and `bool`
|
|
|
|
Prompt IDs and profile IDs are configuration values, not hardcoded stage logic.
|
|
|
|
Trim config shape:
|
|
|
|
```yaml
|
|
trim:
|
|
enabled: true
|
|
output_path: "transcripts/trimmed.json"
|
|
bounds:
|
|
prompt_id: "dnd_session.bounds"
|
|
profile_id: ""
|
|
transcript_input_name: "transcript"
|
|
output_path: "artifacts/session_bounds.json"
|
|
timeout: "10m"
|
|
render_debug: false
|
|
render_output_path: "artifacts/session_bounds.render.json"
|
|
seriatim:
|
|
report: false
|
|
```
|
|
|
|
## 6. Transcript Tiers
|
|
|
|
Narratio currently produces and uses four transcript tiers:
|
|
|
|
- `transcripts/merged.json`: canonical deterministic merged transcript from Seriatim merge
|
|
- `transcripts/processed.json`: full raw Audita-polished transcript output (includes pre/post-game content)
|
|
- `transcripts/normalized.json`: normalized transcript generated by Seriatim normalize
|
|
- `transcripts/trimmed.json`: gameplay-only normalized polished transcript from trim stage
|
|
|
|
Trim reads `transcripts/normalized.json`, validates bounds IDs against that same transcript ID space, and writes `transcripts/trimmed.json`.
|
|
|
|
## 7. Normalize Stage (Current Implementation)
|
|
|
|
Normalize stage behavior:
|
|
|
|
- stage order position: after `polish` and before `trim`
|
|
- discovers processed transcript from manifest polish outputs (`transcript_processed`) when present, else `work/<session_id>/transcripts/processed.json`
|
|
- validates processed transcript JSON shape (`segments` array required)
|
|
- runs Seriatim `normalize` to produce normalized transcript
|
|
- validates normalized transcript JSON shape (`segments` array required)
|
|
- validates normalize report JSON when enabled
|
|
|
|
Expected normalize outputs and diagnostics:
|
|
|
|
- `transcripts/normalized.json`
|
|
- `artifacts/seriatim.normalize.report.json` (when normalize report is enabled)
|
|
- `logs/seriatim.normalize.stdout.log`
|
|
- `logs/seriatim.normalize.stderr.log`
|
|
- `config/seriatim.normalize.generated.yml`
|
|
|
|
## 8. Trim Stage (Current Implementation)
|
|
|
|
Trim stage behavior:
|
|
|
|
- stage order position: after `normalize` and before `analyze`
|
|
- discovers normalized transcript from manifest normalize outputs (`transcript_normalized`) when present, else `work/<session_id>/transcripts/normalized.json`
|
|
- validates normalized transcript JSON shape (`segments` array required)
|
|
- when `trim.enabled: false` (or trim config omitted), deterministically copies normalized transcript to `transcripts/trimmed.json` and records `trim_action=copy_disabled`
|
|
- when `trim.enabled: true`:
|
|
- runs Scriptorium bounds prompt using configured `trim.bounds.prompt_id`
|
|
- writes bounds output to configured path (typically `artifacts/session_bounds.json`)
|
|
- parses and validates bounds output against the same normalized transcript being trimmed
|
|
- converts bounds range to Seriatim keep selector (for example `10-868`)
|
|
- runs Seriatim `trim` to produce `transcripts/trimmed.json`
|
|
- supports no-trim bounds actions (`none`/`copy`) by copying normalized transcript unchanged
|
|
- validates trimmed transcript JSON shape (`segments` array required)
|
|
|
|
Expected trim outputs and diagnostics:
|
|
|
|
- `artifacts/session_bounds.json`
|
|
- `transcripts/trimmed.json`
|
|
- `logs/scriptorium.bounds.stdout.log`
|
|
- `logs/scriptorium.bounds.stderr.log`
|
|
- `config/scriptorium.bounds.generated.yml`
|
|
- `logs/seriatim.trim.stdout.log`
|
|
- `logs/seriatim.trim.stderr.log`
|
|
- `config/seriatim.trim.generated.yml`
|
|
- optional bounds render-debug outputs when enabled:
|
|
- `artifacts/session_bounds.render.json`
|
|
- `logs/scriptorium.bounds.render.stdout.log`
|
|
- `logs/scriptorium.bounds.render.stderr.log`
|
|
- `config/scriptorium.bounds.render.generated.yml`
|
|
|
|
Render-debug files are diagnostics. They are recorded in stage metadata/log/config refs and are not treated as canonical stage output artifact refs.
|
|
|
|
## 9. Analyze Stage (Current Implementation)
|
|
|
|
The current real analyze implementation supports only `scriptorium.artifacts.session_recap`.
|
|
|
|
Behavior:
|
|
|
|
- if `pipeline.scriptorium` is missing, analyze returns a skipped result with metadata
|
|
- if no Scriptorium artifacts are enabled, analyze returns a skipped result with metadata
|
|
- if enabled artifacts exist but `session_recap` is not enabled, analyze fails clearly
|
|
- available transcript input sources for configured artifacts: `processed_transcript`, `normalized_transcript`, `trimmed_transcript`
|
|
- `session_recap` should use `trimmed_transcript` input (`transcripts/trimmed.json`) for in-universe recap generation
|
|
- `trimmed_transcript` input is resolved from manifest (`trim` output kind `transcript_trimmed`) when available, otherwise fallback path `work/<session_id>/transcripts/trimmed.json`
|
|
- `normalized_transcript` input is resolved from manifest (`normalize` output kind `transcript_normalized`) when available, otherwise fallback path `work/<session_id>/transcripts/normalized.json`
|
|
- `processed_transcript` input is resolved from manifest (`polish` output kind `transcript_processed`) when available, otherwise fallback path `work/<session_id>/transcripts/processed.json`
|
|
- `normalized_transcript` is the preferred full-transcript source for future table/meta-analysis artifacts
|
|
- `processed_transcript` remains available for advanced/debug use cases
|
|
- transcript inputs are validated as JSON with top-level `segments` array
|
|
- configured inputs are resolved by source
|
|
- optional `previous_recap` is omitted when unavailable
|
|
- required `previous_recap` fails before invocation when unavailable
|
|
- vars are built from config + session metadata
|
|
- `render_debug` controls pre-run `scriptorium render` diagnostics
|
|
- render failure stops stage before production run
|
|
- render output is validated as JSON
|
|
- production call uses Scriptorium adapter `RunArtifact`
|
|
- successful run output must exist and be non-empty
|
|
- missing `trimmed_transcript` input for configured `trimmed_transcript` source fails clearly with guidance to run trim stage first
|
|
- manifest records output refs, logs, generated config paths, and non-secret provenance metadata
|
|
|
|
## 10. Session Recap Paths
|
|
|
|
Current expected paths for `session_recap`:
|
|
|
|
- artifact output: `artifacts/session_recap.md`
|
|
- run stdout log: `logs/scriptorium.session_recap.stdout.log`
|
|
- run stderr log: `logs/scriptorium.session_recap.stderr.log`
|
|
- run generated invocation/config: `config/scriptorium.session_recap.generated.yml`
|
|
- render output (when enabled): `artifacts/session_recap.render.json`
|
|
- render stdout log: `logs/scriptorium.session_recap.render.stdout.log`
|
|
- render stderr log: `logs/scriptorium.session_recap.render.stderr.log`
|
|
- render generated invocation/config: `config/scriptorium.session_recap.render.generated.yml`
|
|
|
|
## 11. Security and Privacy
|
|
|
|
- do not store secrets in pipeline YAML, generated invocation YAML, logs, or manifest metadata
|
|
- if API-key integration is configured, pass env var names only (never raw key values)
|
|
- avoid logging transcript content or rendered prompt content by default
|
|
- treat generated artifacts and logs as potentially sensitive session material
|
|
|
|
## 12. Operational Caveat (Pre-Stale-Detection)
|
|
|
|
Checksum-based stale detection is not implemented yet.
|
|
|
|
If prepared inputs or prompt/runtime configuration change (for example glossary files, prompt IDs, profile IDs, or relevant pipeline settings), rerun the appropriate prior stages to refresh downstream artifacts.
|
|
|
|
Examples:
|
|
|
|
- glossary or autocorrect changes usually require rerunning at least `merge`, `polish`, `normalize`, `trim`, and `analyze`
|
|
- trim prompt/profile changes require rerunning at least `normalize`, `trim`, and `analyze`
|
|
- session recap prompt/profile/input-source changes require rerunning `analyze`
|
|
|
|
## 13. Roadmap
|
|
|
|
Planned next steps:
|
|
|
|
- extend analyze beyond `session_recap` to additional configured artifacts
|
|
- support artifact inputs that consume prior generated artifacts
|
|
- keep this composable without adding a generic DAG engine in the near term
|
|
- implement real `archive` backend behavior
|
|
- implement real `notify` backend behavior
|
|
- add checksum-based stale detection and stale transitions
|
|
|
|
Architectural invariants remain:
|
|
|
|
- strict config decoding/validation
|
|
- manifest-driven run control
|
|
- clear stage/adapter separation
|
|
- configuration-driven prompt/profile/input/vars/output mapping
|
|
- Scriptorium integration through public CLI subprocess contract
|