Compare commits

5 Commits

19 changed files with 1704 additions and 1484 deletions

450
README.md
View File

@@ -1,449 +1,37 @@
# scriptorium
Scriptorium is a generic prompt execution engine.
Scriptorium is a config-driven prompt execution engine.
It takes:
- a prompt definition
- a selected or default execution profile
- named input artifacts
- template variables
- optional runtime overrides
It separates prompt definitions (what to generate) from execution profiles (how to call an OpenAI-compatible model endpoint), then runs or renders a prepared request from named input artifacts.
It returns:
- for `run`: generated artifact, validation result, metadata
- for `render`: prepared/rendered prompt data (no model output)
## Quickstart
## Prompt vs Profile
From the repository root:
Scriptorium separates **what** to do (Prompt) from **how** to do it (Profile).
### Prompt Definition
Defines the task logic and output contract.
- Task description and version.
- Message templates (system, user, etc.).
- Required and optional input artifacts.
- Output format and validation rules.
- Repair settings for structured output.
- Optional `default_profile` for convenience.
### Execution Profile
Defines the runtime environment and model settings.
- LLM endpoint (URL).
- Model name.
- Generation parameters: `temperature`, `max_tokens`, `top_p`.
- Runtime settings: `timeout`, `reasoning_effort`.
- API key source via `api_key_env`.
Callers can explicitly provide a `profile_id` to override the prompt's `default_profile`.
## Precedence
Scriptorium uses two precedence layers:
### Application Configuration Precedence
For application-level adapter settings (for example prompt/profile/schema directories, server address, and render output default), precedence is:
1. **CLI Flags**
2. **`config.yml`**
3. **Built-in application defaults**
Application config loading behavior:
- Default config path: `/etc/scriptorium/config.yml`
- Override path: `--config <PATH>` (supported by `run`, `render`, and `serve`)
- If `--config` is provided, the file must exist and be valid.
- If `--config` is omitted, missing `/etc/scriptorium/config.yml` is allowed.
### Runtime Model Precedence
When resolving runtime model settings, Scriptorium follows this precedence model (highest to lowest):
1. **Runtime Overrides**: Provided via CLI flags or HTTP request `model` object.
2. **Execution Profile**: Settings defined in the selected profile.
3. **Application Defaults**: Built-in fallback values.
### Profile Selection Logic
The engine determines which profile to use in this order:
1. Explicit `profile_id` (via `--profile` or HTTP request).
2. The `default_profile` named in the Prompt Definition.
3. Error: If neither is provided and no default exists.
## API Key Policy
To ensure security, Scriptorium does not support raw API keys in configuration files, CLI arguments, or HTTP requests.
- **`api_key_env`**: Profiles and overrides specify the name of an environment variable (e.g., `SCRIPTORIUM_API_KEY`).
- **Runtime Resolution**: The value of the environment variable is read directly from the process environment at runtime.
- **Zero Leakage**: API key values are never included in metadata, logs, or response bodies.
## CLI Usage
Available CLI commands:
- `scriptorium run`
- `scriptorium render`
- `scriptorium serve`
All commands accept `--config <PATH>`.
`prompt_dir` and `profile_dir` may be supplied by CLI flags or `config.yml`:
- `--prompt-dir` or `config.yml` `prompt_dir`
- `--profile-dir` or `config.yml` `profile_dir`
`schema_dir` and `serve` `addr` may also be supplied by `config.yml` where applicable:
- `--schema-dir` or `config.yml` `schema_dir`
- `--addr` or `config.yml` `server.addr`
### `scriptorium run`
Runs a single prompt execution.
**Required Flags:**
- `--prompt`: The prompt ID to execute.
- `--input`: Input mapping `name=path` (repeatable).
**Required Effective Settings:**
- Prompt directory: `--prompt-dir` or `config.yml` `prompt_dir`
- Profile directory: `--profile-dir` or `config.yml` `profile_dir`
**Optional Flags:**
- `--config`: Application config file path. Default discovery path is `/etc/scriptorium/config.yml`.
- `--prompt-dir`: Override prompt directory from config.
- `--profile-dir`: Override profile directory from config.
- `--profile`: Override the prompt's default profile.
- `--var`: Template variable `name=value` (repeatable).
- `--out`: Write output to a file instead of stdout.
- `--llm-base-url`: Override endpoint.
- `--model`: Override model name.
- `--api-key-env`: Override API key environment variable name.
- `--temperature`: Override temperature.
- `--max-tokens`: Override max tokens.
- `--top-p`: Override top_p.
- `--timeout`: Override request timeout (e.g., `30s`, `1m`).
- `--schema-dir`: Base directory for validation schemas.
**Examples:**
Using `config.yml` for prompt/profile directories:
```bash
scriptorium run \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md
```
Overriding config directories explicitly:
```bash
scriptorium run \
--prompt-dir ./prompts \
--profile-dir ./profiles \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md
```
Overriding the profile:
```bash
scriptorium run \
--prompt-dir ./prompts \
--profile-dir ./profiles \
--prompt generic.markdown_summary \
--profile local-quality \
--input transcript=./examples/fixtures/transcript.md
```
Overriding model and runtime values:
```bash
scriptorium run \
--prompt-dir ./prompts \
--profile-dir ./profiles \
--prompt generic.markdown_summary \
--model gpt-4o \
--temperature 0.7 \
--input transcript=./examples/fixtures/transcript.md
```
Using a local OpenAI-compatible vLLM endpoint:
```bash
scriptorium run \
--prompt-dir ./prompts \
--profile-dir ./profiles \
--prompt generic.markdown_summary \
--llm-base-url http://localhost:8000/v1 \
--model meta-llama-3-8b \
--input transcript=./examples/fixtures/transcript.md
```
### `scriptorium render`
Prepares and renders a prompt without calling the LLM.
`render` uses the same prompt/profile/input/variable/runtime override resolution as `run`:
- Profile selection precedence: `--profile` -> prompt `default_profile` -> error.
- Runtime precedence: CLI runtime overrides -> selected profile -> built-in defaults.
`render` is useful for debugging:
- prompt template rendering
- input mappings
- selected profile behavior
- runtime override behavior
`render` does not:
- call the LLM
- validate model output
- perform repair
- expose resolved API key values
It may include `api_key_env` names where relevant.
**Required Flags:**
- `--prompt`: The prompt ID to render.
- `--input`: Input mapping `name=path` (repeatable).
**Required Effective Settings:**
- Prompt directory: `--prompt-dir` or `config.yml` `prompt_dir`
- Profile directory: `--profile-dir` or `config.yml` `profile_dir`
**Optional Flags:**
- `--config`: Application config file path. Default discovery path is `/etc/scriptorium/config.yml`.
- `--prompt-dir`: Override prompt directory from config.
- `--profile-dir`: Override profile directory from config.
- `--profile`: Override the prompt's default profile.
- `--var`: Template variable `name=value` (repeatable).
- `--out`: Write output to a file instead of stdout.
- `--format`: Render output format (`text` or `json`). Default: `text`.
- `--llm-base-url`: Runtime override for endpoint.
- `--model`: Runtime override for model name.
- `--api-key-env`: Runtime override for API key environment variable name.
- `--temperature`: Runtime override for temperature.
- `--max-tokens`: Runtime override for max tokens.
- `--top-p`: Runtime override for top_p.
- `--timeout`: Runtime override for timeout (e.g., `30s`, `1m`).
**Render Output Formats:**
- `text`: Human-readable output (default).
- `json`: Machine-readable structured output.
Render formatting is modular; additional output formats can be added later without changing prepare/run core logic.
**Examples:**
Default text output using `config.yml` directories:
```bash
scriptorium render \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md
```
Explicit config path:
```bash
scriptorium render \
go run ./cmd/scriptorium render \
--config ./examples/config.yml \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md
```
Explicit directory overrides:
```bash
scriptorium render \
--prompt-dir ./prompts \
--profile-dir ./profiles \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md
```
Explicit JSON output:
```bash
scriptorium render \
--prompt-dir ./prompts \
--profile-dir ./profiles \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md \
--input glossary=./examples/fixtures/glossary.yml \
--format json
```
Using prompt `default_profile` (omit `--profile`):
```bash
scriptorium render \
--prompt-dir ./prompts \
--profile-dir ./profiles \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md
```
This command renders the prepared prompt and effective runtime settings without calling an LLM.
Overriding profile selection:
```bash
scriptorium render \
--prompt-dir ./prompts \
--profile-dir ./profiles \
--prompt generic.markdown_summary \
--profile local-quality \
--input transcript=./examples/fixtures/transcript.md
```
## Documentation
Overriding runtime settings:
```bash
scriptorium render \
--prompt-dir ./prompts \
--profile-dir ./profiles \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md \
--llm-base-url http://localhost:8000/v1 \
--model gpt-4o-mini \
--temperature 0.2 \
--max-tokens 800 \
--top-p 1.0 \
--timeout 45s
```
Writing rendered output to a file:
```bash
scriptorium render \
--prompt-dir ./prompts \
--profile-dir ./profiles \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md \
--format text \
--out ./rendered_prompt.txt
```
### `scriptorium serve`
Starts the HTTP API.
**Required Effective Settings:**
- Prompt directory: `--prompt-dir` or `config.yml` `prompt_dir`
- Profile directory: `--profile-dir` or `config.yml` `profile_dir`
**Optional Flags:**
- `--config`: Application config file path. Default discovery path is `/etc/scriptorium/config.yml`.
- `--addr`: Listen address (default `:8080`).
- `--schema-dir`: Base directory for validation schemas.
**Examples:**
Using `config.yml`:
```bash
scriptorium serve
```
Overriding config for local use:
```bash
scriptorium serve \
--prompt-dir ./prompts \
--profile-dir ./profiles \
--addr :9090
```
## HTTP API
### `POST /v1/runs`
Executes a prompt. No built-in authentication is provided; deploy behind a trusted gateway.
**Request Body:**
```json
{
"prompt_id": "generic.structured_events",
"profile_id": "local-quality",
"include_raw_output": false,
"inputs": {
"transcript": {"type": "file", "uri": "./examples/fixtures/transcript.md"}
},
"vars": {
"session_date": "2026-05-04"
},
"model": {
"endpoint": "http://localhost:8000/v1",
"model": "gpt-4o-mini",
"temperature": 0.0
}
}
```
`profile_id` is optional. If omitted, Scriptorium uses the prompt's `default_profile`. If neither is available, the run fails.
**Response:**
Returns a `200 OK` with the generated artifact, validation results, and metadata including the `prompt_id` and the `selected_profile_id`.
**Validation Failures:**
If the model output fails validation (e.g., invalid JSON), the API returns `200 OK` with `validation.status = "failed"`.
**Raw Output Exposure:**
- `raw_model_output` is omitted by default.
- Set `include_raw_output: true` in the request to include it in the response.
- Raw output is preserved internally in run results regardless of HTTP exposure.
## Prompt Definition Authoring
Prompts are defined in YAML.
### Canonical Shape
```yaml
id: generic.structured_events
version: "1.0.0"
description: "Extracts structured events from a transcript"
default_profile: local-quality
inputs:
- name: transcript
required: true
content_type: text/markdown
description: "The raw session transcript"
- name: glossary
required: false
content_type: application/yaml
description: "Optional glossary terms"
messages:
- role: system
content: "You are a helpful assistant."
- role: user
content_file: messages/extract_events.tmpl
output:
format: json
validation_mode: json_schema
schema_path: structured_events.schema.json
repair_attempts: 2
```
**Key Features:**
- **Inline vs File**: Use `content` for short prompts or `content_file` for larger templates. Exactly one must be set per message.
- **Path Resolution**: `content_file` paths are resolved relative to the prompt YAML file.
- **Inputs**: Mark inputs as `required` to ensure the runner fails early if they are missing.
- **Input Metadata**: `content_type` is currently descriptive metadata and not enforced yet.
- **Validation**: Support `none`, `basic`, `json`, and `json_schema`.
- **Repair**: `repair_attempts` enables bounded retries to fix structured output.
## Execution Profile Authoring
Profiles are defined in YAML.
### Canonical Shape
```yaml
id: local-quality
endpoint: http://localhost:8000/v1
model: gpt-4o
temperature: 0.0
max_tokens: 4096
top_p: 1.0
timeout_seconds: 300
reasoning_effort: high
api_key_env: SCRIPTORIUM_API_KEY
```
**Constraints:**
- **No Raw Keys**: Do not include actual API keys. Only specify the environment variable name in `api_key_env`.
- **Local Profiles**: For local endpoints that don't require auth, `api_key_env` can be omitted.
- [CLI reference](docs/cli.md)
- [Configuration reference](docs/config.md)
- [Operations guide](docs/operations.md)
- [Troubleshooting](docs/troubleshooting.md)
- [HTTP API integration](docs/integrations/http-api.md)
- [OpenAI-compatible chat integration](docs/integrations/openai-compatible-chat.md)
- [Narratio subprocess integration](docs/integrations/narratio.md)
- [Architecture policy](docs/policy/architecture.md)
- [Documentation roadmap](docs/roadmap/documentation.md)
## Examples
- **Prompt Definitions**: `prompts/`
- **Execution Profiles**: `profiles/`
- **Schemas**: `schemas/`
- **Fixtures**: `examples/fixtures/`
## Build and Test
```bash
go build -o scriptorium ./cmd/scriptorium
go test ./...
```
- `examples/render-markdown-summary.sh`
- `examples/http-run.json`

View File

@@ -1,483 +1,14 @@
# Scriptorium Architecture
# Architecture (Moved)
## 1. Purpose and Non-Goals
The canonical architecture policy is:
Scriptorium is a prompt-definition execution engine.
- `docs/policy/architecture.md`
It accepts named input artifacts, renders prompt templates, calls an LLM, validates output, optionally performs bounded structured-output repair, and returns an artifact with metadata.
Implemented internal component documentation is:
Scriptorium also supports rendering/preparing a prompt without calling an LLM. This allows users to inspect the fully rendered prompt messages and effective runtime settings before executing a run.
- `docs/internal/runner.md`
- `docs/internal/adapters.md`
Scriptorium is not an orchestrator. It must not own transcription, transcript merge/polish steps, notifications, or cross-step workflow control.
Roadmap-only planning content is:
For the motivating D&D workflow:
- Narratio orchestrates.
- WhisperX transcribes.
- Seriatim merges transcripts.
- Audita polishes transcripts.
- Scriptorium generates final artifacts from prepared inputs.
Core Go code remains generic.
## 2. Current Architecture
Scriptorium uses a ports-and-adapters architecture to decouple the core execution logic from external dependencies.
### Package Responsibilities
- `cmd/scriptorium`: Binary entrypoint for CLI and HTTP server.
- `internal/domain`: Core domain contracts, including `PromptDefinition`, `ExecutionProfile`, `PreparedRun`, `RunResult`, and related metadata.
- `internal/usecase`: `Runner` use case logic, including prompt preparation, profile selection, runtime override resolution, full run execution, validation, and bounded repair.
- `internal/config`: Application-level config model/loader for adapter settings (for example prompt/profile/schema directories, server address, and render format default).
- `internal/promptdef`: Repository for loading and validating Prompt Definitions from the filesystem.
- `internal/profile`: Repository for loading Execution Profiles from the filesystem.
- `internal/artifact`: Input artifact resolution (`inline`, `file`).
- `internal/prompt`: Template rendering via Go templates.
- `internal/llm`: Provider-neutral client interface and OpenAI-compatible HTTP adapter.
- `internal/validate`: Output validation implementation (`none/basic/json/json_schema`).
- `internal/adapter/cli`: CLI flag parsing, command dispatch, and output handling.
- `internal/adapter/http`: HTTP request/response mapping.
- `internal/format` or equivalent: Formatting of prepared/rendered prompt output for CLI or other adapters, if formatting grows beyond simple CLI-local helpers.
Exact package names may evolve, but the architectural boundaries should remain stable.
### Application Configuration
`config.yml` is adapter/application setup, not domain logic.
Application config is intended for application-level settings such as:
- `prompt_dir`
- `profile_dir`
- `schema_dir`
- `server.addr`
- `defaults.render_format`
Application config precedence is:
1. CLI flags
2. `config.yml`
3. Built-in application defaults
Runtime model settings are intentionally separate:
- Execution profiles and runtime overrides continue to own endpoint/model/runtime behavior.
- `config.yml` does not replace execution profiles.
The core use case (`Runner.Prepare`/`Runner.Run`) does not need to know whether adapter-level settings came from CLI flags or `config.yml`; it receives resolved dependencies and requests from adapters.
## 3. Core Execution Model
Scriptorium has two closely related execution paths:
1. Prepare/render path.
2. Full run path.
The full run path should reuse the prepare path rather than duplicating its logic.
### 3.1 Prepare / Render Data Flow
The prepare path should be represented in the use case layer, preferably as `Runner.Prepare(ctx, RunRequest)` or an equivalent method.
It executes all pre-LLM work:
1. **Validate Request**: Ensure the request includes a prompt ID and all minimum required fields.
2. **Load Prompt Definition**: Retrieve the `PromptDefinition` by ID from the prompt repository.
3. **Select Profile**: Determine the `profile_id` using this precedence:
- Explicit `profile_id` in `RunRequest`.
- `default_profile` specified in the `PromptDefinition`.
- Error if neither is available.
4. **Load Execution Profile**: Retrieve the `ExecutionProfile` from the profile repository.
5. **Resolve Runtime Overrides**: Merge settings based on precedence, highest to lowest:
- Runtime overrides from CLI flags or HTTP request `model` object.
- Execution Profile settings.
- Built-in application defaults.
6. **Resolve Artifacts**: Load all named input artifacts defined in the request.
7. **Render Prompt**: Apply template variables and input artifacts to the prompt templates.
8. **Compute Metadata**: Compute hashes, selected profile ID, prompt ID/version, effective runtime settings, input hashes, rendered prompt hash, and timing information as appropriate.
9. **Return PreparedRun**: Return a `PreparedRun` containing the rendered messages, effective runtime settings, resolved metadata, and input/prompt hashes.
The prepare path must not call the LLM.
The prepare path must not validate model output, because there is no model output.
The prepare path must not perform structured-output repair, because repair only applies after model output exists.
The prepare path should not resolve or expose raw API key values. It may include the selected `api_key_env` name in effective runtime settings or metadata, but never the environment variable value.
### 3.2 Full Run Data Flow
The `Runner.Run(ctx, RunRequest)` flow should reuse the prepare path:
1. **Prepare**: Call the shared prepare flow to load the prompt, select the profile, resolve artifacts, render prompt messages, and compute pre-run metadata.
2. **Call LLM**: Execute the generation request using the effective runtime settings from the prepared run.
3. **Build Output Artifact**: Convert the model response into the configured output artifact.
4. **Validate Output**:
- Validate the model output against the prompt definition's output contract.
- Validation content failures remain successful run results with `validation.status=failed`.
- Validator runtime/config errors are run errors.
5. **Repair If Configured**:
- If structured validation fails and `repair_attempts > 0`, perform bounded repair attempts.
- Re-validate after each repair attempt.
- Repair loops must remain strictly bounded.
6. **Return RunResult**: Produce a `RunResult` containing the final artifact, validation status, raw model output, usage information, and metadata.
`Runner.Run` should not duplicate profile selection, artifact resolution, or prompt rendering logic that already exists in `Runner.Prepare`.
## 4. Domain Model
Key domain types:
- `PromptDefinition`: Defines the "what" of the task: templates, inputs, output contract, validation settings, repair settings, and optional `default_profile`.
- `ExecutionProfile`: Defines the "how" of execution: endpoint, model, generation parameters, timeout, reasoning effort, and `api_key_env`.
- `RunRequest`: The intent to execute or prepare a prompt, including `prompt_id`, optional `profile_id`, inputs, variables, and optional runtime overrides.
- `PreparedRun`: The result of the prepare/render phase. Contains rendered messages, effective runtime settings, selected profile ID, prompt metadata, input hashes, prompt hash, and other pre-LLM metadata.
- `RunResult`: The result of a full run. Contains the generated `Artifact`, `ValidationResult`, raw model output, token usage, and `RunMetadata`.
- `RunMetadata`: Detailed tracing information, including prompt ID/version, selected profile ID, effective model parameters, usage tokens, hashes, timestamps, validation status, and repair attempts where applicable.
- `RenderedPrompt`: Provider-neutral rendered prompt structure.
- `RenderedMessage`: Provider-neutral rendered message with role and content.
- `ArtifactRef`: A reference to an input artifact, such as `file` or `inline`.
- `Artifact`: Loaded artifact content with name, content type, body, source URI, size, and hash.
- `ValidationResult`: Validation status and details for full runs.
`PreparedRun` should be serializable for JSON output and should also be representable in a human-readable text format.
## 5. Interfaces and Adapters
### Primary Ports
- `promptdef.Repository`: Lookup for prompt definitions.
- `profile.Repository`: Lookup for execution profiles.
- `artifact.Reader`: Loading of artifact content.
- `prompt.Renderer`: Template rendering.
- `llm.Client`: Model generation.
- `validate.Validator`: Output validation.
- `format.PreparedRunFormatter` or equivalent: Optional formatting abstraction for rendered/prepared output.
### Current Adapters
- **Repositories**: Filesystem YAML loaders for both prompts and profiles.
- **Artifact Reader**: Composite reader supporting `file` and `inline`.
- **Prompt Renderer**: Go templates with a custom `input` helper.
- **LLM Client**: OpenAI-compatible `/chat/completions` over HTTP.
- **Validator**: Standard validator supporting `none`, `basic`, `json`, and `json_schema`.
- **CLI Adapter**: Supports `run`, `render`, and `serve`.
- **HTTP Adapter**: Supports full run execution through `POST /v1/runs`.
## 6. Public Contracts
### CLI
Scriptorium should expose at least these commands:
- `scriptorium run`: Executes a prompt by preparing it, calling the LLM, validating output, optionally repairing structured output, and returning an artifact.
- `scriptorium render`: Prepares and renders a prompt without calling the LLM.
- `scriptorium serve`: Starts the HTTP API using infrastructure-only flags.
### `scriptorium run`
`run` uses flags such as:
- `--prompt-dir`
- `--profile-dir`
- `--prompt`
- `--profile`
- `--input`
- `--var`
- `--out`
- runtime overrides such as `--model`, `--llm-base-url`, `--temperature`, `--max-tokens`, `--top-p`, `--timeout`, and `--api-key-env` if supported.
`run` should produce the generated artifact as its primary output.
### `scriptorium render`
`render` prepares and renders a prompt without calling an LLM.
It should use the same prompt/profile/input/variable/runtime override flags as `run` where applicable:
- `--prompt-dir`
- `--profile-dir`
- `--prompt`
- `--profile`
- `--input`
- `--var`
- runtime overrides such as `--model`, `--llm-base-url`, `--temperature`, `--max-tokens`, `--top-p`, `--timeout`, and `--api-key-env` if supported.
- `--format`, with initial support for `text` and `json`.
Default render output format should be `text`.
`render` must not call the LLM.
`render` should show the same rendered messages and effective runtime settings that `run` would use.
`render` should include enough information to debug:
- prompt ID
- prompt version
- selected profile ID
- effective runtime settings
- input hashes
- prompt hash
- rendered messages
`render` must not include resolved API key values.
`render` may include the `api_key_env` name.
### Render Output Formats
Initial render output formats:
- `text`: Human-readable default format.
- `json`: Machine-readable structured representation of the prepared run.
Additional formats, such as `markdown`, may be added later.
Render output formatting should be modular. Adding a new output format should not require changing the prepare/run core logic.
The output formatting layer should consume a `PreparedRun` and produce bytes or text for the adapter. It should not reload prompts, re-resolve artifacts, re-render templates, call the LLM, or perform validation.
### `scriptorium serve`
`serve` starts the HTTP API.
It should use infrastructure-only flags such as:
- `--addr`
- `--prompt-dir`
- `--profile-dir`
- `--schema-dir`
`serve` should not introduce a server-level model/runtime precedence layer unless explicitly documented and intentionally implemented.
### HTTP API
Current HTTP API:
- `POST /v1/runs`: Accepts `RunRequest` JSON and returns `RunResponse` JSON. No built-in auth.
Request may include runtime overrides under `model` and an `include_raw_output` boolean.
`raw_model_output` is exposed only when explicitly requested with `include_raw_output=true`.
A future HTTP prepare/render endpoint may be added, such as `POST /v1/renders` or `POST /v1/runs/prepare`, but the initial render feature may be CLI-only. If added later, it should call the same usecase-level prepare path as `scriptorium render`.
### YAML Shapes
Prompt YAML includes:
- `id`
- `version`
- optional `default_profile`
- `inputs`
- `messages`
- `output`
Inputs support:
- `name`
- `required`
- optional `content_type`
- `description`
Messages require:
- `role`
- exactly one of `content` or `content_file`
`content_file` resolves relative to the prompt YAML location.
Profile YAML includes:
- `id`
- `endpoint`
- `model`
- generation parameters
- timeout settings
- reasoning settings
- `api_key_env`
Prompt content must not appear in profile YAML.
Model/runtime/API-key settings must not appear in prompt YAML, except that prompt YAML may specify `default_profile`.
## 7. Render Feature Design
The render feature is a first-class use case, not a CLI-only shortcut.
### Goals
The render feature should help users:
- inspect fully rendered prompt messages
- debug missing inputs
- verify template variable substitution
- verify selected profile resolution
- verify runtime override precedence
- verify file-backed prompt loading
- inspect input hashes and prompt hashes
- prepare for future token budgeting and prompt-size inspection
### Non-Goals
The render feature should not:
- call an LLM
- validate model output
- repair structured output
- resolve or print API key values
- mutate artifacts
- save outputs to artifact storage unless a future explicit output option is added
- become an orchestration step manager
### Usecase Shape
The preferred usecase shape is:
- `Runner.Prepare(ctx, RunRequest) (*PreparedRun, error)`
- `Runner.Run(ctx, RunRequest) (*RunResult, error)`
`Runner.Run` should call `Runner.Prepare`.
The prepare flow should be the only implementation of:
- prompt loading
- profile selection
- runtime override resolution
- artifact resolution
- prompt rendering
- pre-run metadata/hash calculation
### CLI Shape
The preferred command name is `render`.
The command should support:
- `--format text`
- `--format json`
Default format:
- `text`
Unknown formats should produce a clear error.
Formatting should be centralized through a small formatter registry, strategy, switch, or interface so new formats can be added without modifying usecase logic.
### Text Output Expectations
Text output should be optimized for human inspection.
It should include, at minimum:
- prompt ID and version
- selected profile ID
- model name
- endpoint
- effective generation settings
- input hashes
- rendered prompt hash
- rendered messages grouped by role
Text output should be readable and deterministic enough for tests.
It should not include raw API key values.
### JSON Output Expectations
JSON output should be a structured representation of `PreparedRun` or a DTO derived from it.
It should include, at minimum:
- prompt ID and version
- selected profile ID
- effective runtime settings
- input hashes
- rendered prompt hash
- rendered messages
JSON output should not include raw API key values.
JSON output should remain stable enough to be useful for automation and integration tests.
## 8. Guardrails
- **Separation of Concerns**: Prompt content must not belong in execution profiles; model/API settings must not belong in prompt definitions.
- **Security**: Raw API keys are unsupported in all configuration and transport layers. Only `api_key_env` is used.
- **Secret Handling**: Resolved API key values must never appear in rendered output, metadata, logs, HTTP responses, or CLI output.
- **Path Resolution**: `content_file` paths in prompt definitions resolve relative to the prompt YAML file.
- **Integrity**: No silent prompt truncation or omission of content.
- **Reliability**: Repair loops are strictly bounded by `repair_attempts`.
- **No Orchestration Creep**: Scriptorium prepares and executes a single prompt request. It does not coordinate multi-stage workflows.
- **Render Reuse**: The full run path must reuse the prepare/render path to avoid divergent behavior.
- **Formatter Isolation**: Render output formatters must not perform usecase work. They only format a completed `PreparedRun`.
## 9. Testing Strategy
Tests should protect both the run path and the prepare/render path.
### Prepare / Render Tests
Add tests for:
- preparing a prompt with explicit profile selection
- preparing a prompt using `default_profile`
- failing when no explicit profile and no `default_profile` exist
- runtime overrides beating profile values
- profile values beating application defaults
- file-backed prompt bodies rendering correctly
- required inputs failing when missing
- optional inputs being absent when not referenced
- unknown input references failing
- input hashes being included
- rendered prompt hash being included
- effective runtime settings being included
- `api_key_env` name being included where appropriate
- resolved API key values never appearing in `PreparedRun`
- prepare path not calling the LLM
### CLI Render Tests
Add tests for:
- `scriptorium render` mapping flags into `RunRequest`
- default text output
- explicit `--format text`
- explicit `--format json`
- unknown format failure
- text output includes prompt/profile/messages
- JSON output includes prompt/profile/messages
- rendered output never includes resolved API key values
### Run Reuse Tests
Add tests proving:
- `Runner.Run` reuses prepare behavior
- run and render resolve the same prompt/profile/runtime settings for equivalent inputs
- run still validates output
- run still performs bounded repair where configured
### Existing Tests
Continue testing:
- prompt definition loading
- execution profile loading
- artifact loading
- prompt rendering
- LLM adapter behavior
- validation behavior
- HTTP request/response mapping
## 10. Extension Points
Future work should remain grounded in the current architecture:
- **Artifacts**: Add S3 artifact references via a new `artifact.Reader`.
- **LLM**: Implement additional provider adapters, such as Anthropic or Google.
- **Execution**: Add token budgeting, streaming generation, and batch execution capabilities.
- **Prepare/Render**: Add token estimates, prompt-size summaries, or additional render output formats.
- **Repositories**: Implement database-backed repositories for prompts and profiles.
- **Profiles**: Support more granular profile versioning and environment-specific profiles.
- **HTTP**: Add an HTTP prepare/render endpoint if Narratio or another caller needs it.
Future render formats should plug into the formatter layer and should not require changes to the usecase layer.
- `docs/roadmap/documentation.md`

148
docs/cli.md Normal file
View File

@@ -0,0 +1,148 @@
# CLI Reference
## Shortest Useful Command
```bash
go run ./cmd/scriptorium render \
--config ./examples/config.yml \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md \
--input glossary=./examples/fixtures/glossary.yml
```
`render` prepares and formats the prompt without calling an LLM.
## Command Overview
- `scriptorium run`: prepare prompt, call the configured LLM, write generated output, print a run summary.
- `scriptorium render`: prepare prompt only; write prepared-run output as `text` or `json`.
- `scriptorium serve`: start the HTTP server.
Integration references:
- HTTP contract: `docs/integrations/http-api.md`
- Narratio subprocess contract: `docs/integrations/narratio.md`
## Common Argument Rules
- `--config` is supported by `run`, `render`, and `serve`.
- `run` and `render` require:
- `--prompt`
- at least one `--input`
- an effective `prompt_dir` and `profile_dir` (from flags or config)
- `serve` requires an effective `prompt_dir` and `profile_dir` (from flags or config).
- Positional arguments are rejected.
## Flag Reference
### `scriptorium run`
- `--config <path>`: app config file path.
- `--prompt-dir <dir>`: prompt definition directory.
- `--profile-dir <dir>`: profile definition directory.
- `--schema-dir <dir>`: schema base directory for `json_schema` validation.
- `--prompt <id>`: prompt ID to execute. Required.
- `--prompt-id <id>`: deprecated alias for `--prompt`.
- `--profile <id>`: explicit profile override.
- `--profile-id <id>`: deprecated alias for `--profile`.
- `--input name=path`: input mapping (repeatable, comma-separated accepted).
- `--var name=value`: template variable mapping (repeatable, comma-separated accepted).
- `--out <path>`: write artifact body to file instead of stdout.
- `--llm-base-url <url>`: runtime endpoint override.
- `--model <name>`: runtime model override.
- `--api-key-env <name>`: runtime API key environment-variable name override.
- `--temperature <float>`: runtime temperature override.
- `--max-tokens <int>`: runtime max tokens override.
- `--top-p <float>`: runtime top-p override.
- `--timeout <duration>`: runtime timeout override (Go duration syntax, for example `30s`, `2m`).
### `scriptorium render`
- Supports the same flags as `run`, except:
- no `--schema-dir` flag.
- Adds:
- `--format text|json`: prepared-run output format.
Notes:
- `render` still resolves profile and runtime settings.
- `render` still validates that `api_key_env` exists if the selected profile or overrides require it.
### `scriptorium serve`
- `--config <path>`: app config file path.
- `--addr <listen-address>`: HTTP listen address.
- `--prompt-dir <dir>`: prompt definition directory.
- `--profile-dir <dir>`: profile definition directory.
- `--schema-dir <dir>`: schema base directory for `json_schema` validation.
Notes:
- `serve` does not accept runtime model override flags such as `--model` or `--llm-base-url`.
## Input And Variable Syntax
- `--input name=path` maps prompt input names to local file paths.
- `--var name=value` maps template variable names to values.
- Both flags can be repeated.
- Both flags also support comma-separated batches, for example:
- `--input transcript=./t.md,glossary=./g.yml`
- `--var session_id=42,session_date=2026-05-04`
## Output Behavior
`run`:
- Writes generated artifact content to stdout by default.
- Writes generated artifact content to `--out` when provided.
- Prints run summary metadata to stderr on success.
- Prints errors to stderr on failure.
`render`:
- Writes prepared-run output to stdout by default.
- Writes prepared-run output to `--out` when provided.
- Does not print a success summary line.
`serve`:
- Logs startup and server errors to stderr.
## Exit Codes
- `0`: success.
- `1`: runtime/parse/config/load/render/generation/output-write error.
- `2`: `run` completed, output was generated, but validation status is `failed`.
When `run` exits `2`, output may already be written to stdout or `--out`.
## Common Workflows
Render prompt inputs and template variables as JSON:
```bash
go run ./cmd/scriptorium render \
--config ./examples/config.yml \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md \
--input glossary=./examples/fixtures/glossary.yml \
--var session_date=2026-05-04 \
--format json
```
Run a prompt with profile override and file output:
```bash
go run ./cmd/scriptorium run \
--config ./examples/config.yml \
--prompt generic.markdown_summary \
--profile local-fast \
--input transcript=./examples/fixtures/transcript.md \
--input glossary=./examples/fixtures/glossary.yml \
--out ./out/summary.md
```
Start the HTTP server with explicit config:
```bash
go run ./cmd/scriptorium serve --config ./examples/config.yml
```
Copyable example script:
- `examples/render-markdown-summary.sh`

213
docs/config.md Normal file
View File

@@ -0,0 +1,213 @@
# Configuration Reference
## Config Discovery And Precedence
Application settings are loaded in this order:
1. Built-in defaults
2. `config.yml` values
3. CLI overrides
When `--config` is not provided, Scriptorium searches for config files in this order:
1. `/usr/local/etc/scriptorium/config.yml`
2. `/etc/scriptorium/config.yml`
If neither file exists, Scriptorium continues with built-in defaults.
When `--config <path>` is provided, that file is required.
## Minimal App Config
```yaml
prompt_dir: ./prompts
profile_dir: ./profiles
```
This is enough to use `run` and `render` when prompt/profile files are valid.
## Production-Oriented App Config
```yaml
prompt_dir: /opt/scriptorium/prompts
profile_dir: /opt/scriptorium/profiles
schema_dir: /opt/scriptorium/schemas
server:
addr: 127.0.0.1:8080
defaults:
render_format: text
```
## App Config File (`config.yml`)
Top-level fields:
- `prompt_dir` (optional): default prompt definition directory.
- `profile_dir` (optional): default profile definition directory.
- `schema_dir` (optional): base directory for schema files used by `json_schema` validation.
- `server.addr` (optional): default listen address for `serve`.
- `defaults.render_format` (optional): default `render` output format (`text` or `json`).
Built-in defaults:
- `schema_dir`: `.`
- `server.addr`: `:8080`
- `defaults.render_format`: `text`
Validation behavior:
- Config decoding is strict; unknown YAML fields are rejected.
- Raw API key fields are not supported in `config.yml`.
## Prompt Definition Files
Prompt definitions are YAML files in `prompt_dir`.
Example:
```yaml
id: generic.structured_events
version: "1.0.0"
default_profile: local-quality
description: Produce structured event JSON from a transcript.
inputs:
- name: transcript
required: true
content_type: text/markdown
description: Source transcript content
- name: glossary
required: false
content_type: text/yaml
description: Optional glossary context
messages:
- role: system
content_file: ./generic.structured_events.system.md
- role: user
content_file: ./generic.structured_events.user.md
output:
format: json
validation_mode: json_schema
schema_path: structured_events.schema.json
repair_attempts: 0
```
Field reference:
- `id` (required): prompt identifier.
- `version` (required): prompt version.
- `default_profile` (optional): profile ID used when request does not provide `profile_id`.
- `description` (optional): prompt description.
- `inputs` (optional list): expected named inputs.
- `messages` (required list): prompt message templates.
- `output` (required object): output contract.
`inputs[]` fields:
- `name` (required)
- `required` (optional, boolean)
- `content_type` (optional metadata)
- `description` (optional)
`messages[]` fields:
- `role` (required)
- `content` or `content_file` (exactly one is required)
Message rules:
- Repeated roles are allowed.
- `content_file` is resolved relative to the prompt YAML file location.
- Prompt decoding is strict; unknown YAML fields are rejected.
`output` fields:
- `format` (required): `text`, `markdown`, or `json`.
- `validation_mode` (required): `none`, `basic`, `json`, or `json_schema`.
- `schema_path` (required when `validation_mode: json_schema`).
- `repair_attempts` (required): integer `>= 0`.
Repair behavior boundary:
- `repair_attempts` is part of the prompt contract.
- CLI and HTTP currently construct the runner without a repairer, so normal `run`/`serve` execution does not perform output repair attempts.
## Profile Definition Files
Execution profiles are YAML files in `profile_dir`.
Example:
```yaml
id: local-fast
endpoint: http://localhost:8000/v1
model: gpt-4o-mini
temperature: 0.2
max_tokens: 500
top_p: 1.0
timeout_seconds: 90
api_key_env: SCRIPTORIUM_API_KEY
```
Field reference:
- `id` (required)
- `endpoint` (required)
- `model` (required)
- `temperature` (optional): range `0..2`
- `max_tokens` (optional): `>= 0`
- `top_p` (optional): range `0..1`
- `timeout_seconds` (optional): `>= 0`
- `reasoning_effort` (optional)
- `api_key_env` (optional)
- `extra_params` (optional map of strings)
Profile rules:
- Profile decoding is strict; unknown YAML fields are rejected.
- Raw `api_key` is rejected; use `api_key_env`.
- If `api_key_env` is set, that environment variable must be set when preparing/running.
Current outbound request behavior:
- The OpenAI-compatible client currently serializes: `model`, `messages`, `temperature`, `max_tokens`, `top_p`, and optional `response_format` for `json_schema` prompts.
- `reasoning_effort` and `extra_params` are parsed and carried in effective settings, but are not currently serialized into outbound chat-completions requests.
## Schema Behavior
Schemas are JSON files, typically in `schema_dir`.
Rules:
- `output.validation_mode: json_schema` requires `output.schema_path`.
- Relative `schema_path` values resolve from `schema_dir`.
- Absolute `schema_path` values are used directly.
- Missing or invalid schema documents cause runtime validation errors.
- Invalid generated JSON causes validation status `failed` (not a runtime error).
Supported artifact reference types for request inputs are `file` and `inline`.
## Secrets Handling
- Keep secret values in environment variables.
- Store only environment-variable names in profile `api_key_env`.
- Do not put raw API keys in config, prompts, profiles, CLI flags, or HTTP request bodies.
## Maintained Examples
- App config: `examples/config.yml`
- Prompt examples: `prompts/`
- Profile examples: `profiles/`
- Schema examples: `schemas/`
- Input fixtures: `examples/fixtures/`
- Render example script: `examples/render-markdown-summary.sh`
- HTTP request example: `examples/http-run.json`
## Integration References
- Inbound HTTP contract: `docs/integrations/http-api.md`
- Outbound OpenAI-compatible contract: `docs/integrations/openai-compatible-chat.md`

View File

@@ -1,48 +0,0 @@
# Main `config.yml`
`config.yml` defines application-level defaults used by CLI commands.
By default, Scriptorium looks for `/usr/local/etc/scriptorium/config.yml` and, if not present, then for `/etc/scriptorium/config.yml`. You can also pass `--config PATH`.
## Complete Example
```yaml
prompt_dir: ./prompts
profile_dir: ./profiles
schema_dir: ./schemas
server:
addr: :8080
defaults:
render_format: text
```
## Available Options
- `prompt_dir` (optional): Default directory for prompt definition YAML files.
- `profile_dir` (optional): Default directory for execution profile YAML files.
- `schema_dir` (optional): Base directory for JSON schema files used by `json_schema` validation.
### `server`
- `addr` (optional): HTTP server listen address for `scriptorium serve`.
### `defaults`
- `render_format` (optional): Default output format for `scriptorium render`.
- Allowed values: `text`, `json`.
## Precedence
For run/render/serve settings, precedence is:
1. Explicit CLI flags
2. `config.yml`
3. Built-in defaults
## Notes and Rules
- Unknown YAML fields fail to load (strict decoding).
- This file does not accept API keys.
- `config.yml` sets directory/server defaults only; prompt/profile content remains in their own files.

View File

@@ -1,41 +0,0 @@
# Execution Profile Definitions
Execution Profiles define **how** Scriptorium calls an LLM endpoint.
A profile file is YAML, typically stored under `profiles/`, for example `profiles/local-quality.yaml`.
## Complete Example
```yaml
id: local-quality
endpoint: http://localhost:8000/v1
model: gpt-4.1
temperature: 0.0
max_tokens: 1200
top_p: 1.0
timeout_seconds: 180
reasoning_effort: medium
api_key_env: SCRIPTORIUM_API_KEY
extra_params:
provider: openrouter
route: fallback
```
## Available Options
- `id` (required): Unique profile identifier used by `--profile` or prompt `default_profile`.
- `endpoint` (required): OpenAI-compatible base URL, usually ending in `/v1`.
- `model` (required): Model name to request at that endpoint.
- `temperature` (optional): Sampling temperature. Valid range is `0` to `2`.
- `max_tokens` (optional): Max completion tokens. Must be `>= 0`.
- `top_p` (optional): Nucleus sampling parameter. Valid range is `0` to `1`.
- `timeout_seconds` (optional): Request timeout in seconds. Must be `>= 0`.
- `reasoning_effort` (optional): Provider/model-specific reasoning level string.
- `api_key_env` (optional): Environment variable name that holds the API key.
- `extra_params` (optional): String key/value map for provider-specific parameters.
## Notes and Rules
- Raw API keys are not supported. Do **not** add `api_key` fields.
- Unknown YAML fields fail to load (strict decoding).
- If `api_key_env` is set, the environment variable must be present when the run executes.

View File

@@ -1,73 +0,0 @@
# Prompt Definition Files
Prompt Definitions define **what** Scriptorium should do.
A prompt file is YAML, typically stored under `prompts/`, for example `prompts/generic.structured_events.yaml`.
## Complete Example
```yaml
id: generic.structured_events
version: "1.0.0"
default_profile: local-quality
description: Extract events from a transcript into structured JSON.
inputs:
- name: transcript
required: true
content_type: text/markdown
description: Source transcript
- name: glossary
required: false
content_type: text/yaml
description: Optional glossary context
messages:
- role: system
content: |
You are a structured extraction assistant.
Return only JSON.
- role: user
content_file: ./generic.structured_events.user.md
output:
format: json
validation_mode: json_schema
schema_path: structured_events.schema.json
repair_attempts: 1
```
## Available Options
- `id` (required): Prompt identifier used by `--prompt` / `prompt_id`.
- `version` (required): Prompt version string.
- `default_profile` (optional): Execution profile ID used when no explicit profile is provided.
- `description` (optional): Human-readable description.
### `inputs[]`
- `name` (required): Logical input name referenced in templates via `{{input "name"}}`.
- `required` (optional): If `true`, run fails when input is missing.
- `content_type` (optional): Metadata only (not enforced yet).
- `description` (optional): Human-readable input description.
### `messages[]`
- `role` (required): Message role such as `system` or `user`.
- `content` (optional): Inline Go-template message body.
- `content_file` (optional): Path to a template file.
Each message must set **exactly one** of `content` or `content_file`.
### `output`
- `format` (required): One of `text`, `markdown`, `json`.
- `validation_mode` (required): One of `none`, `basic`, `json`, `json_schema`.
- `schema_path` (required when `validation_mode: json_schema`): Path to JSON Schema file.
- `repair_attempts` (required): Number of bounded repair retries (`>= 0`).
## Notes and Rules
- Unknown YAML fields fail to load (strict decoding).
- `content_file` paths are resolved relative to the prompt YAML file.
- For `json_schema` validation mode, Scriptorium also sends provider-level structured output requests automatically.

View File

@@ -1,82 +0,0 @@
# JSON Schema Definition Files
Schema definition files describe the expected JSON output contract for prompts that use:
- `output.format: json`
- `output.validation_mode: json_schema`
Schema files are JSON, typically stored under `schemas/`, for example `schemas/structured_events.schema.json`.
## Complete Example
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/structured-events.schema.json",
"title": "Structured Events",
"description": "Expected shape for extracted event output",
"type": "object",
"properties": {
"summary": {
"type": "string",
"minLength": 1,
"description": "High-level session summary"
},
"events": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": { "type": "string" },
"type": {
"type": "string",
"enum": ["discovery", "combat", "social", "travel", "downtime", "other"]
},
"notes": { "type": "string" }
},
"required": ["title", "type"],
"additionalProperties": false
},
"minItems": 0
}
},
"required": ["summary", "events"],
"additionalProperties": false,
"$defs": {
"nonEmptyString": {
"type": "string",
"minLength": 1
}
}
}
```
## Available Options
Scriptorium does not define custom schema keywords. It expects a valid JSON Schema document and passes it to the validator/provider.
Commonly used JSON Schema options include:
- `$schema`: Draft identifier URI.
- `$id`: Schema identifier URI.
- `title`: Human-readable schema title.
- `description`: Human-readable schema description.
- `type`: Expected JSON type (`object`, `array`, `string`, etc.).
- `properties`: Object field definitions.
- `required`: Required object fields.
- `additionalProperties`: Whether undeclared fields are allowed.
- `items`: Array item schema.
- `enum`: Allowed literal values.
- `const`: Single allowed literal value.
- `oneOf`, `anyOf`, `allOf`: Composition rules.
- `minimum`, `maximum`: Numeric bounds.
- `minLength`, `maxLength`, `pattern`: String constraints.
- `minItems`, `maxItems`: Array constraints.
- `$defs`: Reusable local definitions.
- `$ref`: Reference to another schema/definition.
## Notes and Rules
- Schema path comes from prompt `output.schema_path` and is resolved relative to `schema_dir`.
- If schema loading fails for `json_schema` mode, the run fails before the LLM request.
- Keep schemas strict (`additionalProperties: false`) when you want predictable output shape.

View File

@@ -0,0 +1,190 @@
# HTTP API Integration
## Scope
This document defines the implemented inbound HTTP contract for Scriptorium.
Current scope is only:
- `POST /v1/runs`
For CLI behavior, see `docs/cli.md`.
## Endpoint
- Method: `POST`
- Path: `/v1/runs`
- Content type: JSON request/response
Route behavior:
- unknown path: `404 not_found`
- unsupported method on `/v1/runs`: `405 method_not_allowed`
Copyable request example file:
- `examples/http-run.json`
## Request Body
```json
{
"prompt_id": "generic.structured_events",
"profile_id": "local-quality",
"prompt_version": "1.0.0",
"inputs": {
"transcript": {"type": "file", "uri": "./examples/fixtures/transcript.md"},
"glossary": {"type": "inline", "body": "party:\n - Rin"}
},
"vars": {
"session_date": "2026-05-04"
},
"model": {
"endpoint": "http://localhost:8000/v1",
"model": "gpt-4o-mini",
"temperature": 0.0,
"max_tokens": 800,
"top_p": 1.0,
"timeout_seconds": 120,
"reasoning_effort": "medium",
"api_key_env": "SCRIPTORIUM_API_KEY",
"extra_params": {
"route": "primary"
}
},
"include_raw_output": false
}
```
Required fields:
- `prompt_id`
- `inputs` (must contain at least one named input)
Input reference types currently supported by runtime artifact loading:
- `file`
- `inline`
## Strict JSON Rules
Request decoding uses strict JSON field checks:
- unknown request fields are rejected with `400 invalid_json`
- unknown `model` fields are rejected with `400 invalid_json`
- raw API-key payload fields such as `api_key` are rejected as unknown fields
## Success Response
Status: `200 OK`
Response shape:
```json
{
"artifact": {
"name": "output",
"content_type": "application/json",
"body": "{\"summary\":\"...\"}",
"uri": "",
"size": 123,
"hash": "..."
},
"validation": {
"status": "passed",
"mode": "json_schema",
"errors": [],
"schema_path": "structured_events.schema.json",
"repair_attempts": 0,
"is_valid": true
},
"metadata": {
"run_id": "...",
"prompt_id": "generic.structured_events",
"prompt_version": "1.0.0",
"prompt_hash": "...",
"rendered_prompt_hash": "...",
"selected_profile_id": "local-quality",
"model_name": "gpt-4o-mini",
"endpoint": "http://localhost:8000/v1",
"model_params": {
"endpoint": "http://localhost:8000/v1",
"model": "gpt-4o-mini",
"temperature": 0,
"max_tokens": 800,
"top_p": 1,
"timeout_seconds": 120,
"reasoning_effort": "medium",
"api_key_env": "SCRIPTORIUM_API_KEY",
"extra_params": {
"route": "primary"
}
},
"input_hashes": {
"transcript": "..."
},
"usage": {
"prompt_tokens": 11,
"completion_tokens": 22,
"total_tokens": 33
},
"start_time": "2026-05-04T12:00:00Z",
"end_time": "2026-05-04T12:00:01Z",
"duration_ms": 1000,
"validation_mode": "json_schema",
"validation_status": "passed",
"repair_attempts_used": 0
}
}
```
`raw_model_output` is omitted by default.
To include it, send:
- `"include_raw_output": true`
## Validation Failure Behavior
Validation content failures do not map to HTTP error status.
Behavior:
- status remains `200 OK`
- `validation.status` is `failed`
- validation errors are returned in `validation.errors`
## Error Responses
Error body shape:
```json
{
"error": {
"code": "invalid_request",
"message": "prompt_id is required"
}
}
```
Current error mapping (non-exhaustive):
- `400 invalid_json`: malformed JSON or unknown JSON fields
- `400 invalid_request`: missing/invalid request fields
- `400 profile_required`: no explicit `profile_id` and prompt has no `default_profile`
- `400 prompt_load_failed`: prompt definition invalid/unloadable
- `400 profile_load_failed`: profile invalid/unloadable
- `400 artifact_read_failed`: input artifact loading failed
- `400 prompt_render_failed`: template render failed
- `400 api_key_env_missing`: named API-key environment variable is missing
- `404 prompt_not_found`
- `404 profile_not_found`
- `502 llm_failed`: outbound model request failed
- `500 validation_runtime_failed`: validator runtime/schema-load failure
- `500 internal_error`
## Security And Deployment Note
The HTTP adapter has no built-in authentication or authorization.
Deploy behind trusted controls (for example authenticated gateway/reverse proxy and network boundaries).

View File

@@ -1,322 +1,114 @@
# Narratio -> Scriptorium CLI Integration
# Narratio Subprocess Integration
## 1. Purpose
## Purpose
This document defines how Narratio should invoke Scriptorium through the **public CLI**.
This document defines the supported subprocess contract for Narratio invoking Scriptorium through the public CLI.
This is a **subprocess integration contract**, not an internal Go API contract.
This is a CLI contract, not an internal Go package integration.
## 2. Assumptions
## Supported Commands
- `scriptorium` is installed and available on `PATH`.
- Scriptorium is configured with `config.yml`.
- `config.yml` provides `prompt_dir`, `profile_dir`, and `schema_dir` as needed.
- Prompt and profile libraries are already deployed for the environment.
- Narratio provides prepared artifact files (for example polished transcript, glossary, previous recap, campaign notes).
- Initial integration is synchronous subprocess execution.
- Narratio remains the orchestrator.
In normal operation, Narratio does not need to pass `--prompt-dir` and `--profile-dir` if they are supplied by Scriptorium config.
Narratio may pass `--config <PATH>` when it must use a non-default Scriptorium config file.
## 3. Core Commands Narratio May Call
Primary commands for subprocess integration:
Narratio should invoke:
- `scriptorium run`
- `scriptorium render`
For production generation, use `scriptorium run`.
Use `run` for generation.
`scriptorium render` is for debugging, dry-runs, test assertions, and validating command construction without LLM execution.
Use `render` for preflight/debug output without LLM execution.
Note: `scriptorium serve` and HTTP API exist, but they are not the initial integration path.
## Recommended Invocation Shapes
## 4. Command Selection Guidance
- Use `run` to generate an output artifact.
- Use `render` to inspect the prepared prompt and effective settings without calling the LLM.
- Use `render --format json` when Narratio/tests need structured prepare output.
## 5. Recommended `run` Invocation Shape
Production shape:
Run:
```bash
scriptorium run \
--prompt <prompt_id> \
--input transcript=<processed-transcript-path> \
--out <output-artifact-path>
--input transcript=<path> \
--out <artifact_path>
```
Common optional additions:
- `--config <path>`: use a specific Scriptorium config file.
- `--profile <profile_id>`: override prompt default profile.
- `--var name=value` (repeatable): small metadata values.
- `--input name=path` (repeatable): additional named artifacts.
- `--timeout <duration>`: per-run timeout override.
- Runtime model override flags (`--llm-base-url`, `--model`, etc.) only for exceptional/operator-directed cases.
## 6. Recommended `render` Invocation Shape
Human-readable debug shape:
Render:
```bash
scriptorium render \
--prompt <prompt_id> \
--input transcript=<processed-transcript-path> \
--format text
--input transcript=<path> \
--format json
```
Structured debug/test shape:
Narratio may add:
```bash
scriptorium render \
--prompt <prompt_id> \
--input transcript=<processed-transcript-path> \
--format json \
--out <render-debug-path>
```
- `--config <path>`
- `--profile <profile_id>`
- repeatable `--input name=path`
- repeatable `--var name=value`
- runtime overrides when explicitly needed (`--model`, `--llm-base-url`, `--timeout`, etc.)
`render` does **not** call the LLM, does **not** validate model output, and does **not** perform repair.
## Config And Directory Behavior
## 7. Inputs
Narratio can rely on resolved app config or pass explicit paths.
- Pass inputs as repeated `--input name=path` flags.
- `name` must match the Prompt Definition input name.
- Prefer absolute paths, or paths relative to a working directory controlled by Narratio.
- Pass Audita output as the primary transcript input.
- Additional inputs may include glossary, previous recap, campaign notes, event logs, final state maps, or other prompt-specific artifacts.
- Scriptorium reads input files directly; Narratio does not need to inline file content for CLI use.
- default config search order:
1. `/usr/local/etc/scriptorium/config.yml`
2. `/etc/scriptorium/config.yml`
- explicit `--config` requires file existence and valid syntax
- CLI flags override config values
## 8. Variables
## Profile Selection
Use repeated `--var name=value` for small metadata values.
Profile selection follows runner behavior:
Typical examples:
1. explicit `--profile`
2. prompt `default_profile`
3. error if neither is available
- `session_date`
- `session_id`
- `campaign_name`
- `previous_session_id`
- `output_kind`
Narratio should treat prompt/profile IDs as deployment configuration, not hardcoded logic.
Large content belongs in input files, not `--var` values.
## Input And Variable Contract
## 9. Prompt IDs and Output Artifact Types
- Inputs use repeated `--input name=path`.
- Input names must match prompt definition input names.
- Variables use repeated `--var name=value` for small metadata values.
- Prefer file inputs for large content.
Narratio should treat prompt IDs as configuration, not hardcoded business logic.
Narratio config may map stage/output names to prompt IDs, for example:
- session recap prompt
- structured event extraction prompt
- glossary suggestion prompt
- player-facing summary prompt
Prompt IDs used by Narratio should come from the deployed Scriptorium prompt library.
## 10. Profiles
- Prompts may declare `default_profile`.
- Narratio may omit `--profile` to use prompt default profile.
- Narratio may pass `--profile` to force profile selection.
- This enables environment/profile selection like `local-fast`, `local-quality`, `frontier`, `batch`, or test profiles.
- Profile names should generally be Narratio configuration values.
## 11. Runtime Overrides
Supported runtime override flags:
- `--llm-base-url`
- `--model`
- `--api-key-env`
- `--temperature`
- `--max-tokens`
- `--top-p`
- `--timeout`
Guidance:
- Keep normal model/runtime settings in Execution Profiles.
- Use runtime overrides only for explicit per-run exceptions, tests, or operator overrides.
- Never pass raw API keys on the command line.
- `--api-key-env` names an environment variable; Narratio must ensure that variable is set in subprocess environment.
## 12. Config Behavior
- Default config path: `/etc/scriptorium/config.yml`.
- `--config <PATH>` overrides default path.
- Missing default config is allowed by Scriptorium.
- If `--config` is provided explicitly, the file must exist and be valid.
- CLI flags override `config.yml`.
- `config.yml` overrides built-in application defaults.
Narratio can either:
- rely on system default config path, or
- carry an explicit config path and pass `--config`.
## 13. Environment Handling
Subprocess environment recommendations:
## Environment Contract
- Pass through required API-key environment variables referenced by `api_key_env`.
- Do not pass raw API keys as CLI arguments.
- Avoid logging full environment dumps.
- Capture stdout and stderr separately.
- Use a controlled working directory.
- Prefer absolute artifact paths.
- Never pass raw API keys via CLI arguments.
- Keep subprocess environment scoped to required variables.
## 14. Output Handling
## Output And Error Handling
For `scriptorium run`:
`run`:
- Use `--out` when Narratio needs durable artifact files.
- Without `--out`, artifact content is written to stdout.
- Preferred orchestration pattern: always use `--out`, then treat the file as stage output artifact.
- Capture stderr for diagnostics.
- stdout: artifact body unless `--out` is used
- `--out`: writes artifact to file
- stderr: success summary and errors
For `scriptorium render`:
`render`:
- Use `--out` to store render diagnostics.
- Use `--format json` when tests need to inspect selected profile, effective runtime settings, input hashes, prompt hash, and rendered messages.
- stdout: prepared-run output unless `--out` is used
- stderr: errors
## 15. Exit Status and Errors
Narratio should capture stdout and stderr separately.
Current CLI behavior (verified from implementation/tests):
## Exit Status Contract
- `0`: success.
- `1`: runtime/parse/config/load/render/generation/IO error.
- `2`: run completed but output validation failed (`ValidationFailed`).
- `0`: success
- `1`: parse/config/load/render/generation/IO/runtime error
- `2`: run completed but validation failed
Additional details:
A `run` exit code `2` can still produce output (stdout or `--out`).
- On `run`, output artifact write happens before exit code selection. If validation fails, artifact may still be written and exit code is `2`.
- `stderr` carries both errors and normal run summary output; non-empty stderr alone does not imply failure.
- `render` returns `0` on success and `1` on failures.
## Security Notes
Narratio should treat non-zero exit codes as failed stage execution, but may record generated artifact paths if a run exited `2` and output file exists.
- Treat generated artifacts and stderr logs as potentially sensitive.
- Avoid logging full rendered prompts by default in production contexts.
- Use controlled output paths and access controls for persisted artifacts.
## 16. Recommended Narratio Integration Pattern
## Canonical References
1. Build CLI args from Narratio stage configuration.
2. Use subprocess context cancellation/timeout.
3. Pass absolute input paths.
4. Pass `--out` to a session-scoped artifact path.
5. Add `--var` metadata values.
6. Optionally add `--config`.
7. Optionally add `--profile`.
8. Ensure required API-key env vars are present.
9. Run subprocess synchronously.
10. Capture stdout/stderr separately.
11. On success, store output artifact path and invocation metadata in stage artifacts.
12. On failure, store exit code and stderr diagnostics in stage status.
## 17. Suggested Narratio Configuration Shape
Illustrative (not required schema):
```yaml
scriptorium:
config_path: /etc/scriptorium/config.yml
stages:
session_recap:
prompt_id: dnd.session_recap
profile_id: local-quality # optional
inputs: [transcript, glossary, previous_recap]
vars: [session_id, session_date, campaign_name]
output_path_template: artifacts/{session_id}/session_recap.md
timeout: 2m
render_debug: false
```
The key idea: map Narratio stage/artifact names to prompt ID, optional profile, expected inputs, and output destination.
## 18. Testing Strategy for Narratio Integration
- Use `scriptorium render --format json` to verify command construction without LLM calls.
- Use dedicated test prompt/profile libraries for integration tests.
- Use small fixture transcripts.
- Verify missing-input failure behavior.
- Verify prompt `default_profile` behavior.
- Verify explicit `--profile` override behavior.
- Verify `--config` behavior (default and explicit).
- Verify output file creation when `--out` is used.
- Verify stderr capture on failures.
- Avoid real API keys in tests.
## 19. Security and Privacy Notes
- Never pass raw API keys on command line.
- Do not log full rendered prompts by default; transcripts may contain sensitive content.
- Avoid logging prompt content unless explicit debug mode is enabled.
- Treat generated artifacts as potentially sensitive.
- Use session-scoped, access-controlled output paths.
- `api_key_env` names should come from environment management, not embedded secrets.
## 20. Initial D&D Artifact Generation Examples
These are examples only. Use prompt IDs from the deployed prompt library.
Session recap:
```bash
scriptorium run \
--prompt dnd.session_recap \
--input transcript=/work/session-42/transcript.polished.md \
--input glossary=/work/session-42/glossary.yml \
--out /work/session-42/artifacts/session_recap.md
```
Structured events:
```bash
scriptorium run \
--prompt dnd.structured_events \
--input transcript=/work/session-42/transcript.polished.md \
--out /work/session-42/artifacts/structured_events.json
```
Glossary suggestions:
```bash
scriptorium run \
--prompt dnd.glossary_suggestions \
--input transcript=/work/session-42/transcript.polished.md \
--input previous_recap=/work/session-41/artifacts/session_recap.md \
--out /work/session-42/artifacts/glossary_suggestions.md
```
Player-facing summary:
```bash
scriptorium run \
--prompt dnd.player_summary \
--input transcript=/work/session-42/transcript.polished.md \
--input structured_events=/work/session-42/artifacts/structured_events.json \
--out /work/session-42/artifacts/player_summary.md
```
## 21. Non-Goals
Initial Narratio integration should not:
- call Scriptorium internal Go packages
- use HTTP API as the primary path
- expect Scriptorium to read S3 refs directly
- make Scriptorium responsible for Narratio stage state
- make Scriptorium responsible for notification
- require Scriptorium to understand D&D workflow semantics beyond prompt definitions
## 22. Future Extension Notes
Possible later extensions:
- HTTP API integration
- S3 artifact references if Scriptorium adds S3 reader support
- storing render diagnostics alongside generated artifacts
- token budgeting/prompt-size checks
- batch execution if Scriptorium later adds batch support
- CLI behavior: `docs/cli.md`
- Config behavior: `docs/config.md`
- Operations and failure handling: `docs/operations.md`, `docs/troubleshooting.md`

View File

@@ -0,0 +1,110 @@
# OpenAI-Compatible Chat Integration
## Scope
This document defines the outbound LLM contract implemented by `internal/llm/openai_compatible_client.go`.
It documents only fields and behaviors currently serialized by code.
## Endpoint Construction
Request endpoint is built as:
1. choose base URL:
- `GenerateRequest.Target.Endpoint` if set
- otherwise client config `BaseURL`
2. trim trailing slash
3. append `/chat/completions`
Example:
- base URL: `http://localhost:8000/v1`
- final URL: `http://localhost:8000/v1/chat/completions`
## Request Fields Sent
Serialized JSON fields:
- `model` (required after fallback resolution)
- `messages` (role/content pairs from rendered prompt)
- `temperature` (only when non-zero)
- `max_tokens` (only when non-zero)
- `top_p` (only when non-zero)
- `response_format` (only when structured output is provided)
Structured output is currently `json_schema` only, serialized as:
```json
{
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "...",
"strict": true,
"schema": {"type": "object"}
}
}
}
```
## Authentication Header
If `Target.APIKeyEnv` is set:
- resolve environment variable value at request time
- set `Authorization: Bearer <value>`
If the environment variable is unset/empty:
- request fails before HTTP call (`ErrInvalidRequest`)
If `Target.APIKeyEnv` is empty:
- no `Authorization` header is sent
## Timeout Behavior
Base timeout comes from client configuration.
Per-request override:
- if `Target.TimeoutSeconds > 0`, use that value for request timeout
- if `Target.TimeoutSeconds < 0`, request is rejected (`ErrInvalidRequest`)
## Response Expectations
Expected successful response shape (subset used):
- `choices[0].message.content`
- `usage.prompt_tokens`
- `usage.completion_tokens`
- `usage.total_tokens`
Malformed response conditions include:
- invalid JSON
- empty `choices`
- empty `choices[0].message.content`
Malformed responses return `ErrMalformedResponse`.
## Error Handling
- network/request-construction failures: `ErrRequestFailed`
- non-2xx HTTP status: `ErrUnexpectedStatus` (includes status code and trimmed response body snippet)
- malformed response shape/content: `ErrMalformedResponse`
## Unsupported Or Non-Serialized Fields
The following fields may exist in profile/effective settings but are not currently serialized into outbound chat-completions payloads:
- `reasoning_effort`
- `extra_params`
No built-in retries, tool-calls, or multi-request payload modes are implemented in this client.
## Relationship To Runner
When prompt validation mode is `json_schema`, runner prepares a structured-output schema spec and passes it to the client as `StructuredOutput`.
The client only serializes the provider request payload; it does not load schema files itself.

139
docs/internal/adapters.md Normal file
View File

@@ -0,0 +1,139 @@
# Adapter And Repository Internals
## Purpose
This document describes implemented adapter/repository boundaries and their current behavior.
## Adapter Map
- `internal/adapter/cli`: CLI command parsing, app wiring, stdout/stderr handling, exit codes.
- `internal/adapter/http`: HTTP request/response mapping for `POST /v1/runs`.
- `internal/promptdef`: filesystem prompt-definition repository.
- `internal/profile`: filesystem execution-profile repository.
- `internal/artifact`: input artifact reader.
- `internal/prompt`: Go-template renderer.
- `internal/llm`: OpenAI-compatible LLM client implementation.
- `internal/validate`: output validator.
- `internal/format`: prepared-run formatters for `render` output.
## Inputs And Outputs
CLI adapter:
- Input: process args, filesystem config/assets, environment.
- Output: exit code, stdout artifact/prepared output, stderr summaries/errors.
HTTP adapter:
- Input: JSON request body (`runRequestDTO`).
- Output: JSON success/error body with mapped status codes.
Filesystem repositories:
- Input: prompt/profile YAML files.
- Output: normalized domain definitions/profiles or typed errors.
Artifact reader:
- Input: `domain.ArtifactRef`.
- Output: loaded `domain.Artifact`.
LLM adapter:
- Input: `domain.GenerateRequest`.
- Output: `domain.GenerateResponse`.
Validator:
- Input: artifact body + output contract.
- Output: validation result or runtime validation error.
## Boundaries
- Adapters convert external representations to domain requests and back.
- Use-case decisions remain in `internal/usecase`.
- External dependency details stay scoped to adapter packages.
## Config Fields Used
Primary app settings consumed by adapters:
- `prompt_dir`
- `profile_dir`
- `schema_dir`
- `server.addr`
- `defaults.render_format`
Execution profile/request settings used through runner:
- `endpoint`, `model`, `temperature`, `max_tokens`, `top_p`, `timeout_seconds`, `api_key_env`, `reasoning_effort`, `extra_params`
## External Dependencies
- YAML decoding: `gopkg.in/yaml.v3` (strict known-fields mode in config/prompt/profile loaders).
- JSON Schema validation: `github.com/santhosh-tekuri/jsonschema/v6`.
- HTTP client/server: Go standard library.
## Failure Behavior
Strict decoding and input checks:
- config/prompt/profile loaders reject unknown YAML fields.
- HTTP DTO decoder rejects unknown JSON fields.
- raw API key payload fields are rejected by strict decoding in profile/http paths.
Artifact refs:
- Supported reference types: `inline`, `file`.
- Unsupported types return `ErrUnsupportedRefType`.
LLM adapter:
- endpoint appends `/chat/completions`.
- non-2xx responses map to request failure errors.
- malformed responses (including missing/empty first choice content) are errors.
Validator:
- `basic`, `json`, `json_schema` content failures return `ValidationFailed` results.
- schema load/compile/path failures are runtime errors.
HTTP error mapping:
- maps domain/use-case errors to stable HTTP code + error code/message.
- avoids returning internal wrapped-cause details in response payload.
## CLI Adapter Semantics
Implemented commands:
- `run`
- `render`
- `serve`
Behavior highlights:
- `run` exit `2` indicates validation failed after generation.
- `render` does not call the LLM.
- `serve` exposes HTTP handler only; no built-in auth.
- `render` supports `--format text|json`; `render` does not expose `--schema-dir`.
- deprecated aliases `--prompt-id` and `--profile-id` are still accepted.
## Tests To Inspect Before Changing
- `internal/adapter/cli/run_test.go`
- `internal/adapter/http/handler_test.go`
- `internal/promptdef/repository_test.go`
- `internal/profile/repository_test.go`
- `internal/artifact/reader_test.go`
- `internal/prompt/renderer_test.go`
- `internal/llm/openai_compatible_client_test.go`
- `internal/validate/standard_validator_test.go`
- `internal/format/prepared_run_test.go`
## Architectural Invariants
- Adapter packages do not own runner decision logic.
- External request/response strictness is part of contract stability.
- Prepared-render output never includes resolved API key values.
- Outbound OpenAI-compatible request includes only currently serialized fields (`model`, `messages`, optional `temperature`, `max_tokens`, `top_p`, optional `response_format`).

146
docs/internal/runner.md Normal file
View File

@@ -0,0 +1,146 @@
# Runner Internals
## Purpose
`internal/usecase.Runner` is the core use case orchestrator for prompt preparation and execution.
It owns request validation, prompt/profile resolution, runtime-parameter merge, artifact loading, prompt rendering, structured-output setup, LLM invocation, output validation, and result metadata.
## Inputs And Outputs
Primary input type:
- `domain.RunRequest`
Primary output types:
- `domain.PreparedRun` from `Prepare`
- `domain.RunResult` from `Run`
LLM boundary types:
- `domain.GenerateRequest`
- `domain.GenerateResponse`
## Boundaries
`Runner` coordinates the following interfaces:
- `promptdef.Repository`
- `profile.Repository`
- `artifact.Reader`
- `prompt.Renderer`
- `llm.Client`
- `validate.Validator`
- optional `usecase.OutputRepairer`
Transport concerns (CLI flags, HTTP DTO parsing, status-code mapping) stay outside runner.
## Config Fields Used
`Runner` does not read app config files directly.
It receives fully constructed repositories/readers/validators from adapters. Effective behavior depends on adapter wiring, including:
- prompt/profile directories
- schema base directory
- selected profile/runtime overrides in request
## External Adapters Used
`Runner` works with adapter implementations via interfaces. Current wiring from CLI/HTTP uses:
- filesystem prompt/profile repositories
- composite artifact reader
- Go-template prompt renderer
- OpenAI-compatible LLM client
- standard validator
## State And Resume Behavior
`Runner` is stateless across requests.
- No durable run-state storage.
- No built-in resume/skip checkpoints.
- Each `Run`/`Prepare` executes from request inputs and current repositories.
## Failure Behavior
Key error classes surfaced from `Runner`:
- `ErrInvalidRequest`: invalid prompt/profile/request/runtime/API-key-env prerequisites.
- `ErrProfileLoad`: prompt or profile load failures.
- `ErrArtifactLoad`: artifact read failures.
- `ErrPromptRender`: template render failures.
- `ErrLLMGenerate`: model request failures.
- `ErrValidation`: validation runtime failures (including schema load/compile failures).
Validation content failures are not run errors:
- `Run` can succeed with `Validation.Status == failed`.
- CLI maps this to exit code `2`.
- HTTP returns `200` with failed validation details.
## Prepare Flow
`Prepare` performs:
1. validate request basics (prompt ID present).
2. load prompt definition by ID/version.
3. select profile ID:
- explicit request profile ID
- prompt `default_profile`
- otherwise request error
4. load execution profile.
5. merge effective runtime target:
- built-in execution defaults
- selected profile values
- request overrides
6. verify required `api_key_env` environment variable (name only; value is not returned).
7. resolve output contract and structured-output schema payload when `json_schema` mode is active.
8. read input artifacts.
9. render prompt messages.
10. compute prompt/input/render hashes and return `PreparedRun`.
`Prepare` does not call the LLM.
## Run Flow
`Run` performs:
1. generate run ID.
2. call `Prepare`.
3. call LLM with prepared messages/effective target/structured-output spec.
4. build output artifact content type from output format.
5. validate output.
6. optionally attempt bounded repair when repairer is injected and contract allows it.
7. return `RunResult` with artifact, raw output, validation, hashes, profile/model metadata, usage, and timestamps.
## Repair Hook Boundary
Repair attempts occur only when all are true:
- repairer is injected
- `repair_attempts > 0`
- validation status is `failed`
- validation mode is `json` or `json_schema`
Current production wiring boundary:
- CLI and HTTP adapters call `usecase.NewRunner(...)` (no repairer argument).
- Therefore normal CLI/HTTP execution does not perform repair attempts today.
## Tests To Inspect Before Changing
- `internal/usecase/runner_test.go`
- `internal/usecase/integration_test.go`
- `internal/adapter/cli/run_test.go`
- `internal/adapter/http/handler_test.go`
## Architectural Invariants
- `Run` reuses `Prepare`; prepare logic is not duplicated.
- Effective API-key environment-variable name may appear; resolved secret value must not.
- Structured-output schema document must load before LLM call for `json_schema` mode.
- Repair loops are bounded by `repair_attempts` and repairer presence.
- Runner stays transport-agnostic.

124
docs/operations.md Normal file
View File

@@ -0,0 +1,124 @@
# Operations Guide
## Scope
This document covers day-to-day operation of the CLI and HTTP service for currently implemented behavior.
For command syntax, see [CLI reference](cli.md). For file formats and defaults, see [Configuration reference](config.md).
## Operational Model
Scriptorium executes one request at a time per CLI invocation or HTTP request.
Important boundaries:
- No durable run state is stored.
- No built-in resume, checkpoint, archive, or backup workflow exists.
- Recovery is rerun-based: fix inputs/config, then rerun.
## Filesystem Layout And Config
Scriptorium depends on:
- prompt definition files (`prompt_dir`)
- execution profile files (`profile_dir`)
- optional JSON schemas (`schema_dir`)
Config discovery order when `--config` is omitted:
1. `/usr/local/etc/scriptorium/config.yml`
2. `/etc/scriptorium/config.yml`
If neither exists, built-in defaults are used. If `--config <path>` is provided, that file must exist and parse successfully.
Built-in defaults relevant to operations:
- `schema_dir: .`
- `server.addr: :8080`
- `defaults.render_format: text`
## Normal CLI Workflow
Use `render` first when you need to verify prompt resolution and runtime settings without calling a model.
Use `run` for generation.
Typical sequence:
1. Confirm prompt/profile directories resolve through config or flags.
2. Confirm required input files exist and map to prompt input names.
3. Confirm required API-key environment variables are set.
4. Run `render` for preflight when changing prompt/profile/input wiring.
5. Run `run` for actual generation.
## Secrets Handling
Raw API keys are not accepted in config files, profile files as `api_key`, CLI flags, or HTTP request bodies.
Operational pattern:
- Set environment variables that hold secret values.
- Set profile `api_key_env` (or runtime override `api_key_env`) to the environment variable name.
- Keep process environments scoped to only required variables.
## HTTP Service Operation
Start service with:
```bash
go run ./cmd/scriptorium serve --config ./examples/config.yml
```
Current inbound API behavior:
- Route: `POST /v1/runs`
- JSON request parsing rejects unknown fields.
- Validation content failures still return `200 OK` with `validation.status: "failed"`.
Security caveat:
- `serve` has no built-in authentication or authorization.
- Deploy only behind trusted controls (private network boundary, authenticated reverse proxy, API gateway, or equivalent).
## Output, Logs, And Exit Codes
`run` command:
- Generated artifact body goes to stdout by default.
- `--out` writes generated artifact to a file.
- Summary metadata line is written to stderr on success.
- Exit code `2` means generation completed but validation failed.
`render` command:
- Prepared-run output goes to stdout by default.
- `--out` writes prepared-run output to a file.
- Exit code is `0` on success and `1` on failure.
`serve` command:
- Startup and server errors are written to stderr.
## Validation Behavior In Operations
Validation modes (`none`, `basic`, `json`, `json_schema`) are defined by prompt output contract.
Operational interpretation:
- Validation runtime errors are hard failures (`run` exit `1`; HTTP error response).
- Validation content failures are soft failures (`run` exit `2`; HTTP `200` with failed status).
A failed validation run can still produce output. Decide whether to keep or discard that output in your surrounding workflow.
## Safe Recovery Steps
For failed runs or requests:
1. Capture stderr output or HTTP error code/message.
2. Confirm config path and directory settings.
3. Verify prompt/profile IDs and input mappings.
4. Verify API-key environment-variable presence when required.
5. Reproduce with `render --format json` when prompt/profile/input resolution is uncertain.
6. Rerun after correction.
Because Scriptorium does not persist run state, rerun is the canonical recovery path.

View File

@@ -1,97 +1,112 @@
# Architecture
This document defines the development principles for this Go project. It is inward-facing: developers and LLM coding agents should use it to preserve the projects shape, boundaries, and invariants as the code evolves.
This document is the development architecture policy for Scriptorium.
It is for developers and LLM coding agents. User-facing behavior belongs in `README.md` and the docs under `docs/` that target operators/users.
## Project Shape
Default to a small, explicit, dependency-light Go application. Keep the design modular enough to test and change safely, but do not add abstraction unless it protects a real boundary or enables a real extension point.
Scriptorium is a narrow prompt-execution application with three entry paths:
Business/domain logic should live outside CLI, transport, and external-adapter packages.
- CLI `run`
- CLI `render`
- HTTP `POST /v1/runs` through `serve`
## Dependency Policy
Domain behavior is centralized in `internal/usecase` and `internal/domain`.
Prefer the Go standard library where practical.
## Core Principles
Use external dependencies only when justified by correctness, security, interoperability, or substantial complexity reduction. Good reasons include complex security-sensitive behavior, such as HTML sanitization, or widely used de facto standards, such as YAML parsing.
- Keep orchestration narrow: Scriptorium executes one prompt request; it is not a multi-step workflow engine.
- Keep adapter logic thin: adapters map external shapes to domain requests/results and should not hold domain decisions.
- Keep boundaries explicit: repositories/loaders/renderers/validators/LLM client stay behind package interfaces.
- Keep config strict: YAML/JSON decoding for external inputs should reject unknown fields.
- Keep secrets out of payloads: raw API key values must not be accepted or emitted.
Avoid dependencies for small conveniences. Do not let external dependency types leak across internal package boundaries unless the dependency is itself the explicit public contract of that package.
## Package Boundaries
## Package Layout
Current package map:
Use this layout unless the project has a documented reason to differ:
- `cmd/scriptorium`: process entrypoint.
- `internal/adapter/cli`: command parsing, app wiring for CLI commands, output behavior.
- `internal/adapter/http`: HTTP DTO mapping and error/status mapping.
- `internal/config`: application settings loading and CLI override precedence.
- `internal/defaults`: compile-time default constants.
- `internal/domain`: core request/result and contract types.
- `internal/usecase`: `Runner` prepare/run orchestration and repair-hook boundary.
- `internal/promptdef`: filesystem prompt-definition repository.
- `internal/profile`: filesystem execution-profile repository.
- `internal/artifact`: artifact reference readers.
- `internal/prompt`: template renderer.
- `internal/llm`: provider-neutral LLM client interface and OpenAI-compatible implementation.
- `internal/validate`: validator interfaces and standard implementation.
- `internal/format`: prepared-run output formatting.
- `internal/app`: application orchestration and top-level use cases.
- `internal/cli`: CLI command definitions, flags, argument parsing, and command wiring.
- `internal/config`: configuration structs, defaults, loading, precedence, and validation.
- `internal/adapters/<name>`: adapters for external CLIs, APIs, databases, object stores, or libraries.
- `internal/api`: HTTP API handlers and request/response types, when the application exposes an HTTP API.
- `internal/transport/http`: HTTP client code, when the application calls HTTP services.
Detailed component behavior is documented in:
Package-private implementation constants may live near the package that owns them, preferably in `constants.go` when useful.
- `docs/internal/runner.md`
- `docs/internal/adapters.md`
## Configuration
## Configuration And Precedence
Centralize configuration loading, processing, precedence, defaults, and validation in `internal/config`.
Application settings are resolved as:
The goal is to make configuration discoverable and avoid implicit or hidden operational values. User-visible defaults and cross-package operational defaults should be defined in `internal/config/defaults.go`.
1. built-in defaults
2. config file values
3. CLI overrides
Unless documented otherwise, precedence is:
`config.yml` is for application wiring (directories, server address, render default format), not prompt/profile runtime execution settings.
1. CLI flags
2. environment variables
3. configuration file
4. built-in defaults
Profile selection and runtime model resolution remain use-case concerns.
Prefer YAML configuration unless the project has a strong reason to use another format. Config files should be discovered at `/usr/local/etc/<app_name>/config.yml`, with a CLI override via `--config`.
## State And Persistence Policy
Configuration files should not contain raw secrets unless the application is explicitly designed for that. Prefer environment variables or secret files for secrets.
Scriptorium has no durable run-state store.
## Adapters and External Integrations
- No built-in resume/checkpoint/archive behavior.
- Recovery model is rerun after correcting inputs/config/environment.
Use a hexagonal architecture style for external integrations.
## External Integration Policy
External adapters belong under `internal/adapters/<name>`. If an adapter uses an external dependency, that dependencys interface must not leak outside the adapter package. Other packages should interact only with the adapters API, so the dependency can be swapped, upgraded, or removed without touching unrelated code.
Current external contracts:
Adapters should be thin. Domain decisions belong in application/domain packages, not inside adapter glue.
- inbound HTTP contract: `POST /v1/runs`
- outbound model contract: OpenAI-compatible chat completions subset
- subprocess contract for integrators: CLI `run`/`render`
## Modules, Stages, and Registries
Integration docs belong under `docs/integrations/`.
When the application has stages or modules, each major stage/module should live in its own package and have an explicit input/output contract.
## Error Handling And Logging
The orchestrator should be able to compose, skip, resume, or run individual stages/modules when their prerequisites are satisfied. Ordering should be explicit: use a default sequence, dependency graph, or documented orchestration rule.
- Wrap errors with domain/operation context.
- Map domain errors to adapter-appropriate statuses/codes without leaking sensitive internals.
- Keep stderr summaries concise for CLI success/error paths.
- Never emit raw secret values.
If users can select modules, stages, validators, renderers, or adapters, selection should go through a registry or equivalent mechanism rather than scattered conditionals.
## Testing Expectations
## Embedded Assets
- Core runner behavior should be covered with isolated unit tests and fixture-based integration tests.
- Adapter behavior should be tested for parse/mapping/error semantics.
- Config parsing, prompt/profile loading, validator behavior, and LLM client error handling should remain covered by package tests.
- Repository-level docs/examples that claim runnable behavior should be validated by tests or smoke commands.
Store embedded JSON schemas, Markdown prompts, templates, and similar assets as separate files, not inline string literals, unless there is a strong reason otherwise.
## Documentation Expectations
## Errors and Logging
- Document implemented behavior only outside `docs/roadmap/`.
- Keep canonical reference locations stable (`docs/cli.md`, `docs/config.md`, `docs/operations.md`, `docs/troubleshooting.md`, `docs/internal/`).
- Update docs in the same change when architecture-relevant behavior changes.
Errors should be actionable and preserve context. Wrap errors with operation and path/resource context. CLI code should convert internal errors into concise user-facing messages.
## Architectural Invariants
Errors and logs must not expose secrets.
- `Runner.Run` reuses `Runner.Prepare` flow.
- CLI and HTTP currently instantiate `Runner` without a repairer.
- Artifact reading supports `inline` and `file` references.
- Unknown input fields in config/prompt/profile/http JSON should be rejected by strict decoding.
- Raw API key values must not be accepted through config/HTTP payloads.
Use structured logging where practical. Logs should describe operations, paths, external calls, retries, and failure causes, but should not include large user data by default.
## Non-Goals
## Context, Timeouts, and Cancellation
- Do not move orchestration responsibilities from external callers into Scriptorium.
- Do not add adapter-specific business logic in `internal/adapter/*` packages.
- Do not bypass repository/renderer/validator/LLM boundaries by introducing cross-package coupling.
Long-running operations should accept `context.Context`. External calls, subprocesses, HTTP requests, storage operations, and multi-stage workflows should respect cancellation and timeouts.
## State, Files, and Safety
If the application writes durable state, writes should be atomic where practical. Multi-step workflows should preserve enough state to support inspection, retry, or resume after failure.
Code that deletes, moves, or overwrites files must use narrow, explicit paths. Avoid broad parent-directory operations. Cleanup that can cause data loss must be opt-in.
## Testing
Core logic should be testable without real external services. Use fakes, fixtures, or local test doubles for adapters where practical.
Config examples should be load-tested. Important CLI workflows should have parser or command tests. Stage/module contracts should have focused tests that do not require running the full application unless end-to-end coverage is intentional.
## Documentation
Documentation should follow the project documentation policy. Keep user docs focused on implemented behavior. Put future, planned, or aspirational work only under `docs/roadmap/`.
When changing architecture, config, CLI behavior, adapters, or stage/module contracts, update the relevant docs and examples in the same change.
Work that is not implemented belongs in `docs/roadmap/`.

103
docs/policy/development.md Normal file
View File

@@ -0,0 +1,103 @@
# Development Guide
This document defines contributor workflow for Scriptorium.
## Repository Layout
- `cmd/scriptorium`: application entrypoint.
- `internal/domain`: core contracts.
- `internal/usecase`: runner orchestration.
- `internal/adapter/cli`: CLI adapter.
- `internal/adapter/http`: HTTP adapter.
- `internal/config`: application settings loading and precedence.
- `internal/defaults`: default constants.
- `internal/promptdef`: prompt-definition repository.
- `internal/profile`: execution-profile repository.
- `internal/artifact`: artifact readers.
- `internal/prompt`: prompt rendering.
- `internal/llm`: LLM client interface and OpenAI-compatible implementation.
- `internal/validate`: validation interfaces and implementation.
- `internal/format`: prepared-run formatting.
- `docs/`: canonical documentation.
- `examples/`: copyable maintained examples and fixtures.
## Common Commands
Build:
```bash
go build ./cmd/scriptorium
```
Test:
```bash
go test ./...
```
Targeted test runs commonly used during changes:
```bash
go test ./internal/adapter/cli ./internal/adapter/http ./internal/usecase
```
## Coding Conventions
- Prefer small interfaces at package boundaries.
- Keep adapter packages focused on translation and IO concerns.
- Keep domain/use-case logic outside adapters.
- Wrap errors with operation context.
- Use strict decoding for user-provided YAML/JSON where applicable.
- Avoid introducing dependencies unless they materially reduce risk/complexity.
## Dependency Policy
- Prefer standard library unless an external library is clearly justified.
- Current non-stdlib dependencies are intentionally small:
- `gopkg.in/yaml.v3` for YAML decoding.
- `github.com/santhosh-tekuri/jsonschema/v6` for JSON Schema validation.
- Do not leak dependency-specific types across unrelated package boundaries.
## How To Add App Config Fields
1. Add fields in `internal/config/config.go` (`Config`, `AppSettings`, and/or `CLIOverrides` as needed).
2. Apply defaults in `BuiltInDefaults()` when required.
3. Parse and validate in `applyConfig` / `ApplyCLIOverrides`.
4. Wire the field through the consuming adapter(s).
5. Add/update config tests in `internal/config/config_test.go`.
6. Update canonical docs (`docs/config.md`, and other affected docs).
## How To Add CLI Flags
1. Add flags in `internal/adapter/cli/run.go` for the relevant command.
2. Ensure precedence behavior remains consistent with app config rules.
3. Keep `run`, `render`, and `serve` flag surfaces intentionally scoped.
4. Add/update parser and command tests in `internal/adapter/cli/run_test.go`.
5. Update `docs/cli.md` and any related docs/examples.
## How To Add Adapters Or Adapter Capabilities
1. Define or reuse the appropriate interface boundary in domain/use-case packages.
2. Implement adapter code under `internal/adapter/<name>` (or relevant boundary package).
3. Keep business decisions in `internal/usecase`.
4. Add focused adapter tests for mapping, parse, and error behavior.
5. Document the new/changed boundary in `docs/internal/adapters.md`.
6. If external contract changes, update `docs/integrations/` in the same change.
## How To Update Prompt/Profile/Schema Assets
1. Keep prompt/profile/schema files valid under strict loaders.
2. Keep examples secret-free.
3. Re-run tests that cover prompt/profile/validation behavior.
4. Update `docs/config.md` and any docs that reference changed contracts.
## Documentation Update Expectations
When behavior changes:
1. Update canonical doc locations, not duplicate files.
2. Keep non-roadmap docs limited to implemented behavior.
3. Update links after file moves/renames.
4. Re-run relevant tests and smoke commands.
Docs work is complete only when code/tests/examples/docs agree.

334
docs/troubleshooting.md Normal file
View File

@@ -0,0 +1,334 @@
# Troubleshooting
This guide lists recurring implemented failure modes and safe fixes.
For command syntax, see [CLI reference](cli.md). For configuration and file formats, see [Configuration reference](config.md). For operational behavior, see [Operations guide](operations.md).
## Missing Or Invalid Config File
Symptom:
- CLI errors such as `application config error: config file not found` or `invalid config YAML`.
Likely cause:
- `--config` points to a missing file.
- Config YAML has syntax errors or unknown fields.
Diagnostic step:
```bash
go run ./cmd/scriptorium render --config /path/to/config.yml --prompt generic.markdown_summary --input transcript=./examples/fixtures/transcript.md --input glossary=./examples/fixtures/glossary.yml
```
Safe fix:
- Correct file path.
- Remove unknown fields.
- Fix YAML syntax.
- Keep secrets out of config.
Relevant links:
- [Configuration reference](config.md)
- [CLI reference](cli.md)
## Missing Prompt/Profile Directory Settings
Symptom:
- CLI parse errors saying prompt directory or profile directory is required.
Likely cause:
- Neither CLI flags nor config provide effective `prompt_dir` / `profile_dir`.
Diagnostic step:
- Run the failing command with explicit `--prompt-dir` and `--profile-dir` once to verify.
Safe fix:
- Set `prompt_dir` and `profile_dir` in config, or always pass both flags.
Relevant links:
- [Configuration reference](config.md)
- [CLI reference](cli.md)
## Unknown Or Unsupported Flags
Symptom:
- CLI parse error for an unknown flag.
Likely cause:
- Typo or command mismatch (for example, `serve` with runtime model override flags).
Diagnostic step:
- Compare command against the command-specific flag list.
Safe fix:
- Remove unsupported flags.
- Use `run`/`render` for runtime model overrides.
Relevant links:
- [CLI reference](cli.md)
## Prompt Definition Load Failures
Symptom:
- CLI run/render error from prompt loading.
- HTTP `404 prompt_not_found` or `400 prompt_load_failed`.
Likely cause:
- Prompt ID not found.
- Invalid prompt YAML.
- Invalid prompt contract (for example bad validation mode, message content/content_file rule violation, missing schema path for `json_schema`).
Diagnostic step:
```bash
go run ./cmd/scriptorium render --config ./examples/config.yml --prompt <prompt-id> --input transcript=./examples/fixtures/transcript.md --input glossary=./examples/fixtures/glossary.yml --format json
```
Safe fix:
- Correct prompt ID.
- Fix prompt YAML and contract fields.
- Ensure referenced `content_file` paths exist.
Relevant links:
- [Configuration reference](config.md)
- [CLI reference](cli.md)
## Profile Definition Load Failures
Symptom:
- CLI run/render error from profile loading.
- HTTP `404 profile_not_found` or `400 profile_load_failed`.
Likely cause:
- Profile ID missing/not found.
- Invalid profile YAML.
- Invalid profile values.
- Raw `api_key` field present (rejected).
Diagnostic step:
```bash
go run ./cmd/scriptorium render --config ./examples/config.yml --prompt generic.markdown_summary --profile <profile-id> --input transcript=./examples/fixtures/transcript.md --input glossary=./examples/fixtures/glossary.yml
```
Safe fix:
- Correct profile ID.
- Fix profile YAML and value ranges.
- Replace `api_key` with `api_key_env`.
Relevant links:
- [Configuration reference](config.md)
- [CLI reference](cli.md)
## Input Artifact Read Failures
Symptom:
- CLI run/render error reading input artifacts.
- HTTP `400 artifact_read_failed`.
Likely cause:
- File path in input mapping does not exist or is unreadable.
- Unsupported artifact reference type in HTTP request.
Diagnostic step:
- Verify every mapped file path exists and is readable by the process.
- For HTTP, verify each input uses supported `type` values.
Safe fix:
- Correct file paths and permissions.
- Use supported input types (`file`, `inline`).
Relevant links:
- [CLI reference](cli.md)
- [Configuration reference](config.md)
## Prompt Template Render Failures
Symptom:
- CLI run/render error from prompt rendering.
- HTTP `400 prompt_render_failed`.
Likely cause:
- Template references missing input names.
- Template syntax or data reference issues.
Diagnostic step:
- Run `render --format json` with the same prompt, inputs, vars, and profile selection.
Safe fix:
- Align template `{{input "name"}}` references with actual input mappings.
- Fix template syntax and variable names.
Relevant links:
- [CLI reference](cli.md)
- [Configuration reference](config.md)
## Missing API-Key Environment Variable
Symptom:
- CLI run/render invalid request error about missing API-key environment variable.
- HTTP `400 api_key_env_missing`.
Likely cause:
- Selected profile or override sets `api_key_env`, but that environment variable is unset/empty.
Diagnostic step:
```bash
printenv SCRIPTORIUM_API_KEY
```
Safe fix:
- Set the required environment variable before invoking CLI/service.
- Or use a profile that does not require API key auth for the target endpoint.
Relevant links:
- [Configuration reference](config.md)
- [Operations guide](operations.md)
## LLM Request Failures
Symptom:
- CLI `run` fails with LLM generation errors.
- HTTP returns `502 llm_failed`.
Likely cause:
- Endpoint unreachable.
- Non-2xx response from provider.
- Timeout.
- Malformed provider response.
Diagnostic step:
- Confirm endpoint URL and model in selected profile/overrides.
- Retry with `render` first to confirm pre-LLM preparation works.
- Check provider/network logs for non-2xx responses and timeouts.
Safe fix:
- Correct endpoint/model settings.
- Adjust timeout if needed.
- Resolve provider-side or network issues.
Relevant links:
- [CLI reference](cli.md)
- [Configuration reference](config.md)
- [Operations guide](operations.md)
## Validation Status Failed (`run` Exit 2 Or HTTP 200 With Failed Status)
Symptom:
- CLI exits with code `2`.
- HTTP returns `200`, but `validation.status` is `failed`.
Likely cause:
- Generated output failed `basic`, `json`, or `json_schema` content validation.
Diagnostic step:
- Inspect validation mode and validation errors in CLI summary/HTTP response.
Safe fix:
- Refine prompt constraints.
- Tighten schema or adjust model/profile settings.
- Rerun after correction.
Relevant links:
- [Configuration reference](config.md)
- [Operations guide](operations.md)
## Validation Runtime Failure
Symptom:
- CLI `run` fails with validation runtime error.
- HTTP returns `500 validation_runtime_failed`.
Likely cause:
- `json_schema` schema file missing/inaccessible.
- Invalid schema JSON document.
Diagnostic step:
- Verify `schema_dir` and `output.schema_path` resolution.
- Check schema file readability and valid JSON syntax.
Safe fix:
- Correct schema path.
- Fix schema JSON content.
- Rerun.
Relevant links:
- [Configuration reference](config.md)
- [Operations guide](operations.md)
## HTTP Request Parsing/Contract Errors
Symptom:
- HTTP `400 invalid_json` or `400 invalid_request`.
Likely cause:
- Malformed JSON body.
- Unknown JSON fields.
- Missing required `prompt_id` or `inputs`.
Diagnostic step:
- Revalidate request JSON.
- Confirm required request fields are present.
Safe fix:
- Send valid JSON with only supported fields.
- Ensure `prompt_id` and at least one input mapping are included.
Relevant links:
- [Operations guide](operations.md)
- [CLI reference](cli.md)

18
examples/http-run.json Normal file
View File

@@ -0,0 +1,18 @@
{
"prompt_id": "generic.markdown_summary",
"profile_id": "local-fast",
"inputs": {
"transcript": {
"type": "file",
"uri": "./examples/fixtures/transcript.md"
},
"glossary": {
"type": "file",
"uri": "./examples/fixtures/glossary.yml"
}
},
"vars": {
"session_date": "2026-05-04"
},
"include_raw_output": false
}

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$repo_root"
go run ./cmd/scriptorium render \
--config ./examples/config.yml \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md \
--input glossary=./examples/fixtures/glossary.yml \
--format text