Files
scriptorium/architecture.md

233 lines
8.0 KiB
Markdown

# Scriptorium Architecture
## 1. Purpose and Non-Goals
Scriptorium is a prompt-profile execution engine.
It takes named input artifacts, renders prompt templates, calls an LLM, validates output, optionally performs bounded structured-output repair, and returns an artifact with metadata.
Scriptorium is not an orchestrator. It should not own transcription, transcript merging, transcript polishing, notification, or cross-step workflow control.
For the motivating D&D workflow:
- Narratio orchestrates.
- WhisperX transcribes.
- Seriatim merges transcripts.
- Audita polishes transcripts.
- Scriptorium generates output artifacts from prepared inputs.
Core Go code must remain domain-generic.
## 2. Current Architecture
Current high-level structure:
- `cmd/scriptorium`: binary entrypoint.
- `internal/domain`: core domain types.
- `internal/usecase`: `Runner` use case and repair loop orchestration.
- `internal/profile`: filesystem prompt profile repository and profile validation.
- `internal/artifact`: artifact reference readers (`inline`, `file`) and routing.
- `internal/prompt`: Go template-based prompt renderer.
- `internal/llm`: LLM client interface + OpenAI-compatible HTTP adapter.
- `internal/validate`: output validator implementation (`none/basic/json/json_schema`).
- `internal/adapter/cli`: CLI adapter.
- `internal/adapter/http`: HTTP adapter (`POST /v1/runs`).
This is a practical ports-and-adapters implementation.
## 3. Run Data Flow
`Runner.Run(ctx, RunRequest)` currently executes:
1. Validate minimum request requirements (`profile_id`).
2. Load prompt profile by ID/version.
3. Merge effective model target (profile defaults + request override).
4. Resolve effective output contract (profile + optional request override).
5. Resolve named artifact refs to loaded artifacts.
6. Render prompt messages from templates.
7. Hash rendered prompt for auditability.
8. Call LLM client with provider-neutral `GenerateRequest`.
9. Build output artifact from model content.
10. Validate output.
11. If structured validation failed and repair is enabled/bounded, run repair attempts and re-validate.
12. Return `RunResult` with artifact, validation, raw output, and metadata.
Validation content failure remains a successful run result with `validation.status=failed`.
## 4. Package Responsibilities
- `domain`
- Owns core nouns and contracts.
- Must not import adapters/provider SDK types.
- `usecase`
- Owns execution sequence and cross-port orchestration for a single run.
- May coordinate validation and bounded repair.
- Must not contain HTTP/CLI/wire concerns.
- `profile`
- Owns prompt profile loading/parsing/validation.
- Handles YAML strict decoding and profile-level constraints.
- `artifact`
- Owns artifact ref resolution and content loading.
- Produces normalized `Artifact` values with size/hash/content type.
- `prompt`
- Owns template rendering and required-input enforcement.
- `llm`
- Owns generation port and provider adapters.
- Current adapter: OpenAI-compatible chat completions over `net/http`.
- `validate`
- Owns output validation semantics and JSON Schema integration.
- `adapter/http`, `adapter/cli`
- Owns transport/wire/flag concerns only.
- Should stay thin and delegate business flow to `usecase.Runner`.
## 5. Domain Model (Current)
Key types in `internal/domain`:
- `RunRequest`: profile selector, named input refs, vars, optional model override, optional validation override.
- `RunResult`: output artifact, validation result, raw output, profile/model metadata, hashes, usage, timestamps, duration.
- `ArtifactRef`: `{type, uri, body}` reference contract.
- `Artifact`: loaded payload (`name`, `content_type`, `body`, `uri`, `size`, `hash`).
- `PromptProfile`: YAML-backed profile definition.
- `RenderedPrompt` / `RenderedMessage`: provider-neutral prompt structure.
- `GenerateRequest` / `GenerateResponse`: provider-neutral model I/O.
- `ValidationResult`: passed/failed/skipped + mode/errors/schema/repair attempts.
## 6. Interfaces and Adapters
Primary ports:
- `profile.Repository`
- `artifact.Reader`
- `prompt.Renderer`
- `llm.Client`
- `validate.Validator`
- `usecase.OutputRepairer` (usecase-local abstraction)
Current adapters:
- Profile repository: filesystem YAML loader.
- Artifact reader: composite reader for `inline` and `file`.
- Prompt renderer: Go templates with input helper + vars.
- LLM adapter: OpenAI-compatible `/chat/completions`.
- Validator: standard validator with `none/basic/json/json_schema`.
- CLI/HTTP adapters: thin request mapping and response mapping.
## 7. Validation and Repair Model
Validation modes:
- `none`
- `basic`
- `json`
- `json_schema`
Repair behavior:
- Only applies to structured modes (`json`, `json_schema`).
- Attempted only when validation fails, repairer exists, and `repair_attempts > 0`.
- Bounded strictly by `repair_attempts`.
- Uses a narrow JSON-repair prompt and re-validates each attempt.
- If still invalid, run succeeds with failed validation and preserved final raw output.
- Validator runtime/config errors are run errors.
## 8. Public Contracts
### CLI
Commands:
- `scriptorium run`
- `scriptorium serve`
`run`:
- Required: `--profile-dir`, `--profile-id`, `--input`.
- Optional: model/endpoint overrides (`--model`, `--llm-base-url`), vars, output path, schema dir, timeout.
- Artifact bytes go to stdout (or `--out` file); summaries/errors go to stderr.
`serve`:
- Required: `--profile-dir`, `--llm-base-url`.
- Exposes HTTP run endpoint.
### HTTP
- Endpoint: `POST /v1/runs`.
- Request maps to `RunRequest` (`profile_id`, `inputs`, `vars`, optional `model` override).
- Response includes `artifact`, `validation`, `metadata`, `raw_model_output`.
- Validation content failures are represented as `200` with `validation.status=failed`.
- Error responses are `{error:{code,message}}` with stable code mapping.
### Prompt Profile YAML
- `id`, `version`, `expected_inputs`, `templates`, `model_defaults`, `output_format`, `validation`.
- Strict YAML decoding (`KnownFields`) rejects unknown fields.
- `validation.schema_path` required when `validation_mode=json_schema`.
- `validation.repair_attempts` must be non-negative.
### Metadata
Current run metadata includes:
- `run_id` (UUID v4)
- `profile_id`, `profile_version`, `profile_hash`
- `model_name`, `endpoint`, effective `model_params`
- `input_hashes`, `prompt_hash`
- token usage
- start/end timestamps
- duration
- validation mode/status
- repair attempts used
## 9. Extension Points (Future Work)
Future features should plug into existing boundaries, not bypass them.
Candidate extensions:
- S3 artifact refs via `artifact.Reader` extension.
- Token budgeting in usecase/model-target policy layer.
- Streaming LLM output via additional `llm.Client` methods/adapters.
- Batch execution as a separate use case (not hidden in single-run path).
- Additional LLM providers implementing `llm.Client`.
- Additional validators/modes in `validate`.
- Additional profile repositories (embedded, remote, object storage).
These are future work, not part of current default behavior.
## 10. Architectural Guardrails
Contributors should preserve these constraints:
- No D&D-specific behavior in core Go packages.
- No orchestration creep into Scriptorium.
- No unbounded repair loops.
- No silent truncation/omission of rendered inputs or outputs.
- Do not log full artifacts/prompts by default.
- Keep provider-specific wire/SDK details out of domain types.
- Keep adapter boundaries explicit and thin.
## 11. Testing Strategy
Protect behavior at boundaries and in usecase flow:
- Profile loading/parsing/validation errors.
- Artifact reading for inline/file + hash/content type behavior.
- Prompt rendering required inputs/template error behavior.
- LLM adapter request/response/error/timeout behavior.
- Runner success path and metadata population.
- Validation failure raw-output preservation.
- Successful/failed/bounded repair flows.
- HTTP request mapping, response shape, and error mapping.
- CLI parsing helpers, required flags, and output stream separation.
Prefer focused unit tests and small integration-style tests with fake LLMs.