8.0 KiB
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:Runneruse 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:
- Validate minimum request requirements (
profile_id). - Load prompt profile by ID/version.
- Merge effective model target (profile defaults + request override).
- Resolve effective output contract (profile + optional request override).
- Resolve named artifact refs to loaded artifacts.
- Render prompt messages from templates.
- Hash rendered prompt for auditability.
- Call LLM client with provider-neutral
GenerateRequest. - Build output artifact from model content.
- Validate output.
- If structured validation failed and repair is enabled/bounded, run repair attempts and re-validate.
- Return
RunResultwith 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
Artifactvalues 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.Repositoryartifact.Readerprompt.Rendererllm.Clientvalidate.Validatorusecase.OutputRepairer(usecase-local abstraction)
Current adapters:
- Profile repository: filesystem YAML loader.
- Artifact reader: composite reader for
inlineandfile. - 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:
nonebasicjsonjson_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 runscriptorium 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
--outfile); 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, optionalmodeloverride). - Response includes
artifact,validation,metadata,raw_model_output. - Validation content failures are represented as
200withvalidation.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_pathrequired whenvalidation_mode=json_schema.validation.repair_attemptsmust be non-negative.
Metadata
Current run metadata includes:
run_id(UUID v4)profile_id,profile_version,profile_hashmodel_name,endpoint, effectivemodel_paramsinput_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.Readerextension. - Token budgeting in usecase/model-target policy layer.
- Streaming LLM output via additional
llm.Clientmethods/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.