# ADR 0002: Split Promptkit From Scriptorium ## Status Accepted ## Date 2026-07-26 ## Context Scriptorium currently combines two products in one Go module: - a reusable prompt-execution framework with a public Go facade; and - a runnable application with CLI and HTTP interfaces. Downstream Go projects increasingly import the framework directly and do not use the executable interfaces. Keeping both products in one module couples framework releases, dependencies, documentation, and public API evolution to application-specific transport concerns. Promptkit will become the framework project, and Scriptorium will become a slim application that consumes it. This ADR records that end-state boundary. It does not assert that the split has been implemented; until then, the current repository structure and contracts remain authoritative. ## Decision ### Projects And Module Paths Create a repository named `promptkit` alongside Scriptorium: | Project | Repository and Go module path | Root Go package | | --- | --- | --- | | Promptkit | `gitea.maximumdirect.net/eric/promptkit` | `promptkit` | | Scriptorium | `gitea.maximumdirect.net/eric/scriptorium` | No reusable root facade after migration | Promptkit will expose its supported public API from the module root. Its implementation packages will remain under `internal/` unless a real consumer extension point requires a public type or interface. Scriptorium will import only Promptkit's supported public packages. It will not import Promptkit implementation packages or reproduce Promptkit orchestration. ### Product Responsibilities Promptkit owns application-neutral framework behavior: - the engine and its `Prepare` and `Run` workflow; - public request, result, profile, option, extension, and error APIs; - prompt-definition loading and rendering; - profile loading, overlays, and the embedded built-in profile registry; - schema loading and output validation; - provider-neutral model-client boundaries and the OpenAI-compatible client; - artifact types, artifact-reader injection, and general-purpose inline and caller-selected file readers; - execution-setting resolution and framework defaults; and - framework-level secret redaction and error classification. Scriptorium owns executable and transport behavior: - the `scriptorium` process and its `run`, `render`, and `serve` commands; - CLI parsing, streams, output files, formatting, exit codes, and process cancellation behavior; - application-configuration discovery and CLI-over-configuration precedence; - HTTP routing, strict request decoding, DTO mapping, response encoding, status codes, and transport limits; - HTTP artifact-root containment and deployment policy; - server construction, server defaults, and process logging; and - executable release artifacts. The dependency direction is: ```text Scriptorium CLI and HTTP adapters | v Promptkit public API | v injected sources, readers, and model clients ``` ### Current Package Disposition Implementation may reorganize files during extraction, but each current package has this target owner: | Current package or file group | Target owner | Disposition | | --- | --- | --- | | Root `scriptorium` facade files and tests | Promptkit | Move and rename the public package to `promptkit`; Scriptorium retains no compatibility facade. | | `internal/domain`, `internal/usecase` | Promptkit | Move as internal engine implementation. | | `internal/promptdef`, `internal/prompt` | Promptkit | Move as internal prompt loading and rendering. | | `internal/profile`, `internal/profile/builtin` | Promptkit | Move with embedded built-in assets and registry tests. | | `internal/filecatalog` | Promptkit | Move as source-loading support. | | `internal/validate` | Promptkit | Move as schema and output-validation implementation. | | `internal/llm` | Promptkit | Move with the OpenAI-compatible integration. | | `internal/artifact` | Split | Move general inline/file reading to Promptkit; keep rooted, denied, and byte-limited HTTP file reading in Scriptorium behind a Promptkit reader interface. | | `internal/defaults` | Split | Move framework, execution, output-artifact, content-type, and model-client defaults to Promptkit; keep CLI, HTTP, and server defaults in Scriptorium. | | `internal/adapter/cli`, `internal/adapter/http` | Scriptorium | Keep and refactor to use Promptkit's public API. | | `internal/config` | Scriptorium | Keep application settings, discovery, validation, and CLI precedence. | | `internal/format` | Scriptorium | Keep prepared-run presentation, rewritten against Promptkit public values. | | `cmd/scriptorium` | Scriptorium | Keep as the process entrypoint. | Tests move with the behavior they protect. Cross-boundary tests will live with the consuming side: Promptkit protects framework contracts, while Scriptorium protects adapter mapping, HTTP containment, and executable behavior. ### Public Boundary Promptkit's initial facade will preserve the useful shape of the current Scriptorium Go API where that reduces extraction risk. It will expose only the capabilities required by Promptkit consumers and by Scriptorium: - engine construction, preparation, and execution; - public request, result, profile, and error values; - prompt, profile, schema, artifact-reader, validator, and model-client source or injection options that have demonstrated consumers; and - enough stable error identity for Scriptorium to map CLI and HTTP outcomes. Promptkit will not export its domain package, runner implementation, repositories, adapter DTOs, or general internal constructors merely to simplify the move. Scriptorium's CLI and HTTP adapters will depend on a small consumer-facing `Prepare`/`Run` interface where test substitution is needed. That interface belongs at the consuming boundary rather than forcing adapter concepts into Promptkit. ### Artifact Reading And HTTP Containment Promptkit will define the artifact-reader extension point used during preparation. Its ordinary file reader may read a path deliberately supplied by an in-process or CLI caller and does not claim to be a deployment sandbox. Scriptorium will implement the HTTP-specific reader that: - denies file references when no artifact root is configured; - applies the configured artifact byte limit; - enforces Scriptorium's documented lexical root-containment rule; and - maps reader failures to Scriptorium HTTP error responses. Scriptorium will inject that reader through Promptkit's public construction boundary. Promptkit will not know about HTTP roots, status codes, request DTOs, or deployment policy. ### Configuration And Default Ownership Configuration ownership follows the behavior configured, not the current file location: | Configuration category | Owner | | --- | --- | | Application configuration discovery, configuration-file precedence, `prompt_dir`, `profile_dir`, and `schema_dir` | Scriptorium | | CLI flags and their mapping to application settings or request overrides | Scriptorium | | `server.*`, render-output settings, HTTP byte limits, and server defaults | Scriptorium | | Prompt-definition, profile, and output-contract file formats | Promptkit | | Prompt/profile source selection, overlays, schema behavior, and built-in profiles | Promptkit | | Execution settings, presence-aware request overrides, and execution defaults | Promptkit | | Built-in OpenAI-compatible client settings, timeout behavior, and provider wire mapping | Promptkit | | HTTP request and response fields, including their mapping to framework values | Scriptorium | Scriptorium will translate its application settings and external request values into Promptkit construction options and requests. When an omitted Scriptorium setting means “use the framework default,” Scriptorium will omit the override rather than copy Promptkit's numeric default. ### Compatibility And Versioning This migration is intentionally breaking: - new Go consumers will import `gitea.maximumdirect.net/eric/promptkit`; - Scriptorium will not provide aliases, forwarding wrappers, or deprecated compatibility packages for its former Go facade; - existing consumers may remain pinned to the final framework-bearing Scriptorium tag until migrated; and - intermediate migration phases need not preserve source compatibility, but each merged phase must be internally buildable and tested. Promptkit's first release will be `v0.1.0`. During the migration, incompatible Promptkit changes may advance its minor version until a stable `v1` contract is declared. The first slim Scriptorium release will advance the Scriptorium minor version beyond the final framework-bearing release. Normal semantic-versioning rules apply independently to both projects after the migration. Promptkit must be tagged before Scriptorium or another consumer publishes a release that depends on it. Release branches must use tagged module dependencies, not local replacements or unpublished revisions. ### Local Development And Cross-Repository Coordination For coordinated local work, place both repositories in a temporary Go workspace or use an uncommitted module replacement. `go.work`, `go.work.sum`, and local filesystem `replace` directives must not be committed to release branches. Cross-repository changes follow this order: 1. land and tag the required Promptkit capability; 2. update Scriptorium and other consumers to that tag; 3. run each repository's own CI and smoke checks; and 4. release consumers only after the Promptkit tag is available. Migration coordination must confirm out-of-band repository creation, Promptkit tags, and downstream migrations before dependent work proceeds. Cross-repository changes are coordinated, not treated as atomic commits. ### Documentation And Maintained Assets Each repository will maintain its own README, contributor guide, architecture, documentation, testing, release, and operations material appropriate to that project. Cross-project documents will link to the canonical owner rather than copy its contract. Existing documentation and maintained assets have these target owners: | Current material | Target owner | | --- | --- | | Current README and executable quickstart | Scriptorium; Promptkit creates its own framework orientation | | Public Go package and Go-consumer guidance | Promptkit | | Prompt, profile, schema, execution-setting, and framework credential reference | Promptkit | | OpenAI-compatible integration contract and framework internal documents | Promptkit | | CLI, HTTP API, subprocess, and Scriptorium operations contracts | Scriptorium | | Consumer interface overview | Scriptorium, revised to route Go consumers to Promptkit | | Application-configuration discovery, server settings, and adapter internals | Scriptorium | | Current internal overview and source documentation | Split into repository-local overviews; Promptkit owns framework sources and Scriptorium owns HTTP containment | | This ADR and cross-project migration records | Scriptorium | | `examples/go-library` | Promptkit | | `examples/config*.yml`, `examples/render-markdown-summary.sh`, and `examples/http-run.json` | Scriptorium | | Example prompts, profiles, schemas, and synthetic fixtures used by the executable examples | Scriptorium | | Embedded built-in profile assets | Promptkit | | Scriptorium release workflow and executable packaging | Scriptorium | | Repository-level license, ignore rules, agent guidance, and development policies | Each repository maintains its own applicable copy | Promptkit will create or retain its own minimal framework examples and test fixtures rather than making either repository's tests depend on the other's working tree. Scriptorium's framework-format documentation will become a short version-appropriate link to Promptkit, while its maintained executable examples remain self-contained. ## Alternatives Considered - Keep the current combined repository and improve package naming. This avoids migration work but retains release and ownership coupling between the framework and executable. - Add Promptkit as a wrapper around the Scriptorium public package. This gives consumers a new import path but leaves framework ownership and dependency direction inverted. - Extract Promptkit while retaining a Scriptorium compatibility facade. This reduces immediate consumer changes but creates a second public API surface and prolongs duplicate maintenance. - Move all artifact reading into Promptkit. This would place HTTP containment, byte limits, and deployment policy in the application-neutral framework. - Keep Promptkit and Scriptorium as separate modules in one repository. This separates imports but not repository permissions, release workflows, issue ownership, or independent project evolution. ## Rationale A separate Promptkit project makes the reusable framework the direct owner of the API that downstream Go projects already consume. Keeping Scriptorium as a public-API consumer exercises the same boundary as other consumers and prevents its adapters from relying on framework internals. The selected split keeps transport and deployment policy close to the Scriptorium interfaces that expose it, while allowing Promptkit to remain useful to in-process consumers with different IO and security requirements. Explicit package, configuration, documentation, and asset ownership reduces ambiguity during extraction and after release. ## Consequences - All Go consumers of the framework must change their import path. - Promptkit and Scriptorium gain independent issue, release, CI, policy, and documentation lifecycles. - Scriptorium becomes a real downstream integration test of Promptkit's public facade. - Framework changes that affect Scriptorium require tagged, ordered cross-repository coordination. - Some current packages, especially artifact reading and defaults, must be separated by responsibility rather than moved intact. - Scriptorium's current configuration and documentation references must be split between application and framework owners. - Maintainers must inventory and migrate downstream consumers explicitly; no compatibility facade will hide incomplete migration. - Until the split is implemented, the current repository structure and contracts remain authoritative.