28 KiB
Promptkit v0.9.0 Adoption Implementation Plan
Status And Authority
Planned. Implement the stages below in numeric order. The accepted feature scope and target state are defined by the Promptkit v0.9.0 adoption roadmap, and the durable product boundary is defined by ADR 0004. If this plan and either source conflict, the ADR governs product boundaries and the feature roadmap governs scope.
Follow the architecture, documentation, and testing policies under
docs/policy/ throughout the work. In particular:
- use only Promptkit's public root package and its released v0.9.0 tag;
- do not add a workspace, module replacement, vendored Promptkit source, direct catalog import, or Scriptorium reimplementation of Promptkit parsing and validation;
- keep default tests deterministic, offline, and free of real credentials or provider calls;
- update each current-state contract in the same stage that implements its behavior; and
- prefer a small number of boundary and integration tests over duplicating Promptkit's own framework test matrix.
At the start of every stage, inspect the working tree and preserve unrelated
changes. At the end of every stage, run the focused tests named in that stage,
gofmt changed Go files, and run git diff --check. Do not proceed while a
stage's completion conditions are unmet.
Stage 1: Adopt The Promptkit v0.9.0 Compatibility Baseline
Completion: Complete.
Make the released dependency the build baseline before adding new application surface area.
Implementation
- Update
go.modto requiregitea.maximumdirect.net/eric/promptkit v0.9.0, then rungo mod tidysogo.modandgo.sumcontain the module graph selected by that tag. Accept Promptkit's OpenRouter and Rakestrawhome catalog modules only as transitive dependencies; do not import them from Scriptorium. - In the HTTP request DTO, change
model.reasoning_effortfromstringto*string, and pass the pointer directly topromptkit.ExecutionTargetOverride.ReasoningEffort. This is required by the v0.9.0 API and preserves omitted versus explicit-empty input. Keep the JSON field itself a string when present. - Compile every Promptkit public struct literal against v0.9.0. Keep literals
keyed, do not restore the removed
RunRequest.Metadatafield, and do not add compatibility wrappers around Promptkit. - Audit maintained prompt, profile, schema, configuration, and HTTP examples
against v0.9.0. Correct only actual incompatibilities: endpoints must be
absolute HTTP(S) URLs, roles must be
developer,system,user, orassistant, positive repair budgets must be at most three and paired withbasic,json, orjson_schema, content and schema paths must remain within their Promptkit source rules, and extra parameters must be JSON-compatible and avoid reserved provider keys. - Replace v0.1.0 links and current-version statements in current-state
documentation with tagged v0.9.0 links. Do not rewrite historical version
statements in accepted ADRs. Update the owning documentation to state the
v0.9.0 credential and provider behavior: an optional missing environment
credential may result in an unauthenticated provider request, while a
profile that declares credentials required still fails as an invalid request
when no source is supplied and with
ErrAPIKeyEnvMissingwhen its selected environment source is empty; unset optional provider controls are omitted; and a positive repair budget can add provider calls, latency, token use, and cost. - Preserve the architecture guard for forbidden replacements, workspaces,
vendor trees, former facade packages, and Promptkit
internalimports. Extend it only if the dependency graph introduces a new realistic bypass that the existing assertions do not cover.
Verification
- Update HTTP mapping tests for the pointer-valued reasoning field, including
separate omitted and explicit-empty cases, and update
docs/api.mdwith the HTTP three-state behavior in this stage. Stage 4 adds the corresponding CLI control and session-ID support. - Run
go test ./...,go test -race ./...,go vet ./..., andgo build ./cmd/scriptoriumagainst the tagged module with no local replacement. - Run the existing maintained render example and any example-validation tests.
- Confirm that a search for
v0.1.0finds only legitimate historical material, and that searches forreplace,go.work, and Promptkit/internal/imports do not reveal a prohibited dependency path.
Completion Conditions
The repository builds and all existing behavior tests pass on Promptkit v0.9.0; current documentation identifies v0.9.0 as the framework contract; no local or private Promptkit integration mechanism has been introduced.
Stage 2: Remove Definition-Compatibility Restrictions In Run And Render
Completion: Complete.
Allow the executable adapters to address every valid directory-backed prompt definition without imposing input requirements of their own.
Implementation
- Add
promptVersion stringto the shared run/render request configuration. Register--prompt-version <version>for bothrunandrender, include it in usage text, and map it without local normalization topromptkit.RunRequest.PromptVersion. - Make
--inputoptional forrunandrender. Do not weakenparseMappingsfor callers that actually supplied mappings; instead, skip that parser when no input flag was supplied and pass a nil or empty input map to Promptkit. Promptkit remains responsible for declared-required and template-referenced input failures. - In
POST /v1/runs, remove the adapter check that rejects a nil or emptyinputsobject. Preserve strict JSON decoding and all validation of input references that are present. Continue mapping the already-supportedprompt_versionfield exactly once intoRunRequest.PromptVersion. - Keep extra supplied inputs legal and retain the CLI file-reader and HTTP artifact containment and size policies.
- Update
docs/cli.mdanddocs/api.mdin this stage: prompt ID remains required, prompt version is optional, inputs are optional at the adapter boundary, and Promptkit decides whether the selected definition requires them. Update the shortest request examples only where necessary to avoid implying that all prompts need inputs.
Verification
- Add CLI behavior tests proving that an omitted version selects a sole prompt definition, an explicit version selects the requested definition, and an omitted version fails clearly when a prompt ID has multiple versions.
- Add CLI and HTTP adapter coverage for: a prompt with no declared inputs, omitted optional inputs, a missing declared-required input, and a missing template-referenced input. Use a capturing runner for pure request mapping and one real Promptkit engine integration fixture for the definition-driven failures; do not reproduce Promptkit's parser tests.
- Retain tests for malformed supplied mappings, invalid HTTP input references, artifact containment, and request-size limits.
- Run
go test ./internal/adapter/cli ./internal/adapter/httpand the maintained render workflow.
Completion Conditions
Both executable interfaces can select a specific prompt version and can submit no inputs; only Promptkit rejects missing definition-required data.
Stage 3: Add Engine-Scoped Custom Backend Configuration
Completion: Complete.
Add the application-owned configuration needed for Promptkit profiles to select custom backend IDs and capacity policies.
Configuration Contract
Use this exact strict-YAML shape:
backends:
local-gpu:
endpoint: http://localhost:11434/v1
api_key_env: LOCAL_GPU_API_KEY
extra_params:
provider_option: enabled
concurrency_limit: 2
queue_capacity: 0
The mapping key is the backend ID. endpoint is required by the effective
contract. api_key_env, extra_params, concurrency_limit, and
queue_capacity are optional. concurrency_limit uses zero as Promptkit's
unlimited value. Represent queue_capacity as *int so omission uses
Promptkit's default queue capacity and explicit zero disables queuing. There
are no backend CLI overrides and no raw API-key field.
Implementation
- Add
Backends map[string]BackendConfigto the on-diskconfig.Configand aBackendConfigwith exactly the five YAML fields above. Add a resolvedBackendSettingsvalue containingIDplus the corresponding fields, and carry[]BackendSettingsonAppSettings. - During config application, sort the YAML map keys and build the resolved
slice in that order. Preserve the
queue_capacitypointer and the completeextra_paramsvalue. The deterministic order makes multi-error diagnostics and tests stable; it is not backend precedence. - Do not duplicate Promptkit's backend validators in
internal/config. Strict YAML decoding owns unknown-field and type errors. PromptkitNewEngineowns blank/reserved/duplicate IDs, endpoint and environment-name rules, reserved or invalid extra parameters, and capacity relationships. - Refactor CLI engine construction around a small shared engine-settings value
containing prompt, profile, and schema directories plus resolved backends.
Convert every
BackendSettingstopromptkit.Backend, register it withpromptkit.WithBackend, and use the same constructor forrun,render, andserve. Wrap construction failures with application-configuration and engine-initialization context while preservingerrors.Isidentities. - Retain one immutable backend registry per constructed engine. Do not create registries per HTTP request, and do not add direct imports of Promptkit's catalog modules; Promptkit's built-ins remain available automatically.
- Document
backendsindocs/config.md, including key identity, queue-capacity presence, validation ownership, optional environment credentials, and the raw-secret prohibition. Updatedocs/internal/adapters.mdanddocs/internal/sources.mdto describe the implemented mapping and engine assembly. Add the exact shape toexamples/config.full.yml; keep the minimal config minimal.
Verification
- In
internal/config, test strict decoding of all fields, unknown and raw secret-field rejection, deterministic resolved ordering, JSON-compatible nestedextra_params, and omitted versus explicit-zeroqueue_capacity. - At the CLI/engine boundary, test a valid custom backend selected by a custom
profile through
render, and representative Promptkit validation failures: reserved backend ID, invalid endpoint, invalid capacity relationship, and a reservedextra_paramskey. Assert the stable error identity or a small diagnostic fragment, not Promptkit's full text. - Test that endpoint-only profiles and built-in profiles still work without a
backendssection. - Run
go test ./internal/config ./internal/adapter/cliand render with the complete example without contacting a provider.
Completion Conditions
Every command constructs its engine from the same resolved custom-backend configuration, Promptkit remains the validator, and profiles can select custom, built-in, or endpoint-only execution targets.
Stage 4: Expose Session And Presence-Aware Reasoning Controls
Completion: Complete.
Complete the request mapping for direct session identifiers and the v0.9.0 reasoning override semantics.
Implementation
- Add
--session-id <id>and--reasoning-effort <value>to bothrunandrender, with no aliases. Map the session value toRunRequest.SessionID. - Track flag presence for
--reasoning-effort. Omission leavesExecutionTargetOverride.ReasoningEffortnil, a nonblank value supplies a pointer to that value, and--reasoning-effort=supplies a pointer to the empty string. Include reasoning presence when deciding whether to allocate the enclosing execution override. - Add optional
session_idto the strict HTTP run-request DTO and map it toRunRequest.SessionIDwithout application-level normalization. Keepmodel.reasoning_effortpointer-valued so omitted, nonblank, and empty JSON strings remain distinct. - Add optional
session_idto successful HTTP metadata and populate it from the effectiveRunResult.SessionID, not directly from the request. Useomitempty; absence means no effective session ID. - Continue to reject unknown HTTP fields and do not add
api_key, appended messages, or request-level output-contract fields. - Update
docs/cli.mdanddocs/api.mdwith the exact flag/field names, inheritance/replacement/clearing semantics, the non-secret nature and Promptkit length rules of session IDs, and effective-session response behavior.
Verification
- CLI tests must distinguish an omitted reasoning flag, a nonblank replacement,
and an explicit empty clear by observing the resulting prepared run through
render. Add a mapping assertion that an explicit empty value still creates an execution override. - HTTP tests must capture and distinguish the same three reasoning states and
continue to reject non-string values under the strict DTO contract. Treat
JSON
nulllike omission, matching ordinary pointer-field decoding; the documented explicit clear remains the empty JSON string. - CLI and HTTP tests must cover a direct session ID, a definition-rendered session ID when no direct value is supplied, no effective session ID, and an invalid overlong direct ID. Assert that successful HTTP metadata reports the engine's effective result.
- Run
go test ./internal/adapter/cli ./internal/adapter/http ./internal/format.
Completion Conditions
CLI and HTTP callers can inherit, replace, or clear reasoning effort and can supply a direct session ID, with Promptkit retaining normalization and validation ownership.
Stage 5: Present Backend Identity And Map Capacity Outcomes
Completion: Complete.
Expose Promptkit's selected routing identity and make overload behavior a stable application contract.
Implementation
- In prepared-run text output, add
selected_backend_idimmediately afterselected_profile_idwhen it is nonempty. Preserve direct JSON marshaling ofpromptkit.PreparedRun; v0.9.0 already suppliesselected_backend_idandeffective_model_params.backend_idJSON fields. - In the CLI run summary, append
backend=<id>whenRunResult.SelectedBackendIDis nonempty. Omit the token for endpoint-only profiles rather than deriving an ID from the endpoint. - Add
selected_backend_id,omitemptyto HTTP metadata andbackend_id,omitemptytometadata.model_params, mapping them fromRunResult.SelectedBackendIDandExecutionTarget.BackendIDrespectively. Keep the two values independently mapped so an upstream inconsistency is not hidden by Scriptorium. - In HTTP error mapping, classify
promptkit.ErrCapacityExceededbefore the general generation failure and return HTTP 503 with codecapacity_exceededand messagemodel backend capacity is exhausted. Do not emitRetry-Afterand do not exposeCapacityErrordetails. - In CLI
run, classify the same identity as the existing runtime-error exit status 1 and print the stable safe diagnosticrun error: model backend capacity is exhausted. Keep all other runtime error handling unchanged. - Preserve
serveCommand's one-engine/one-handler construction outside the request path. Do not add per-request engine construction; that would split capacity state and violate ADR 0004. - Update
docs/cli.md,docs/api.md, anddocs/operations.mdwith backend identity, endpoint-only omission, capacity status, lack of retry timing, shared per-engine admission, and the distinction between transient in-flight state and durable workflow state.
Verification
- Formatter, CLI, and HTTP tests must cover a registered backend and an endpoint-only profile. Assert supplied backend IDs and empty/omitted identity rather than endpoint-derived guesses.
- Add HTTP error-table coverage proving capacity is 503
capacity_exceeded, provider failure remains 502llm_failed, and neither response leaks wrapped provider or capacity diagnostics. - Add one offline concurrent HTTP integration test using a single real
Promptkit engine, a custom backend with
concurrency_limit: 1and explicitqueue_capacity: 0, and a blocking fake LLM client. Hold the first request in generation, issue a second request, assert the second receives the 503 contract, release the first, assert it succeeds, and assert observed peak generation concurrency is one. This test owns the Scriptorium shared-engine interaction; do not replicate Promptkit's FIFO or broader scheduler suite. - Run
go test -race ./internal/adapter/http ./internal/adapter/cli ./internal/format.
Completion Conditions
All presentations report actual backend identity when present, endpoint-only profiles remain identity-free, and one server instance enforces one engine's backend capacity across concurrent requests.
Stage 6: Add Stable Prompt Inspection Output And CLI
Completion: Complete.
Introduce reusable application-owned inspection presentation, then expose prompt inspection without model execution.
Presentation Contract
-
Generalize the internal prepared-run format selector to an internal
OutputFormatwithtextandjsonvalues, a text default, and one parser. Update config and prepared-run call sites to use it without changing existing prepared-run bytes ordefaults.render_formatbehavior. -
Define a Scriptorium-owned prompt-inspection DTO; never marshal
promptkit.PromptInspectiondirectly. JSON contains these fields in this shape, including empty strings and an empty array where applicable:{ "prompt_id": "example", "prompt_version": "1", "prompt_hash": "opaque", "default_profile_id": "", "inputs": [ { "name": "source", "required": true, "content_type": "text/plain", "description": "Input text" } ], "output_contract": { "format": "text", "validation_mode": "none", "schema_path": "", "repair_attempts": 0 } } -
Preserve Promptkit's declared input order. Emit JSON with deterministic indentation and one trailing newline. Text output uses the same field order, renders
inputs: []when empty, lists input records in declaration order, quotes free-form descriptions safely, includes empty declared values, and ends in one newline. The prompt hash is opaque and must not be interpreted.
CLI Contract And Implementation
-
Add
inspectto top-level dispatch and usage, with implementation in a new focused CLI file rather than further expandingrun.go. -
Implement:
scriptorium inspect prompt --prompt ID [--prompt-version VERSION] [--config PATH] [--prompt-dir DIR] [--format text|json] [--out PATH]--promptis required. All other flags are optional. Positional arguments and unknown inspection modes are errors. There are no deprecated aliases. -
Apply ordinary config discovery and CLI-over-file precedence. Require an effective prompt directory, pass the optional version directly to
Engine.InspectPrompt, and construct the engine through the shared settings path so configured backends are validated consistently. Do not require a profile or schema directory for this operation. -
Default inspection format to text independently of
defaults.render_format;--formatis the only inspection format override. Reuse ordinary output-file handling. Write output only after successful inspection and formatting. Use exit 0 for success and exit 1 for parsing, configuration, load, formatting, or write failure. -
Update
docs/cli.md,docs/internal/adapters.md, and the internal format component description. Make clear that inspection loads and normalizes a definition but does not resolve a profile, load a schema, render templates, reserve backend capacity, or call a model.
Verification
- Formatter tests cover deterministic text and JSON, declared order, empty inputs/default profile/schema path, all nonempty fields, and nil inspection handling. Prefer structural JSON assertions plus a few stable text fragments over large snapshots.
- CLI tests cover required flags, rejected positional arguments, config source
selection,
--prompt-dirprecedence, sole and explicit multi-version selection, default and explicit formats, stdout versus--out, no partial output on failure, and Promptkit prompt-not-found/load identities. - Use a fake or deliberately failing model client to prove inspection performs no generation; do not assert Promptkit's internal call sequence.
- Run
go test ./internal/format ./internal/adapter/cli.
Completion Conditions
inspect prompt produces stable Scriptorium text or JSON for every valid
directory-backed v0.9.0 prompt definition without inputs or model access, and
prepared-run presentation remains backward compatible.
Stage 7: Add Stable Profile Inspection Output And CLI
Completion: Complete.
Complete the inspection command family with inherited, built-in, custom, and endpoint-only profile support.
Presentation Contract
-
Define a Scriptorium-owned profile-inspection DTO; never marshal
promptkit.ProfileInspectiondirectly. JSON has the following complete shape. Keep empty strings and zero numeric values because Promptkit reports unresolved optional controls as zero, and normalize absentextra_paramsto an empty object:{ "profile_id": "example", "effective_model_params": { "backend_id": "", "endpoint": "https://example.test/v1", "model": "model-name", "temperature": 0, "max_tokens": 0, "top_p": 0, "timeout_seconds": 0, "service_tier": "", "reasoning_effort": "", "api_key_env": "", "extra_params": {} }, "api_key_required": false } -
Text output follows the same order, nests effective model parameters, encodes extra-parameter values as deterministic compact JSON with sorted keys, includes an empty
backend_idfor endpoint-only profiles, and ends in one newline. Never read or print an environment-variable value or direct API key.
CLI Contract And Implementation
-
Implement the second mode:
scriptorium inspect profile --profile ID [--config PATH] [--profile-dir DIR] [--format text|json] [--out PATH]--profileis required. The common inspection parse, format, output, and exit rules from Stage 6 apply. No prompt, prompt directory, schema directory, or input is required, so built-in profiles are inspectable with only their ID. -
Resolve config and
--profile-dirwith normal precedence, construct the engine through the shared settings path so custom backends participate, and callEngine.InspectProfileexactly once. -
Update
docs/cli.md,docs/config.md, and internal adapter/source docs. Explain that profile inspection resolves inheritance and backend defaults but does not read credentials, load a prompt, reserve capacity, or call a model. Document that zero provider-control values may mean unspecified, consistent with Promptkit v0.9.0.
Verification
- Formatter tests cover deterministic text and JSON, registered and empty
backend identity, optional environment-variable names,
api_key_required, nested extra parameters, and the absence of credential values. - CLI tests cover built-in, inherited custom, custom-backend, and endpoint-only
profiles; config and
--profile-dirprecedence; default and explicit format; stdout and--out; missing and invalid profiles; no partial output; and no requirement forprompt_dir. - Include a sentinel environment secret in a test and assert it appears in no inspection output or diagnostic while its variable name may appear.
- Run
go test ./internal/format ./internal/adapter/cli ./internal/config.
Completion Conditions
inspect profile reports the complete safe effective profile view for every
valid directory-backed v0.9.0 profile and configured backend combination
without requiring a prompt or contacting a model.
Stage 8: Complete Cross-Feature Conformance And Release Validation
Close the roadmap with representative definition-level integration coverage, copyable examples, documentation alignment, and full validation. Do not defer contract documentation from earlier stages to this stage; this is a final cross-check.
Implementation And Documentation
-
Maintain one compact shared fixture set under
testdata/promptkit-v0.9/withprompts,profiles,schemas, and any message content files needed by CLI and HTTP integration tests. Through a small set of composable definitions, cover:- inline and file-backed messages;
developer,system,user, andassistantroles and message cache control;- required and optional inputs plus template-referenced values;
- prompt versions, default profiles, and session-ID templates;
- text and JSON output plus
none,basic,json, andjson_schemavalidation, a schema path, and a valid positive repair budget; - standalone and inherited profiles;
- built-in, configured custom, and endpoint-only backends;
- model controls, service tier, reasoning effort, timeout, optional credential environment names, and nested JSON-compatible extra parameters.
Exercise these fixtures through Scriptorium's
renderand inspection boundaries. Assert representative effective outcomes; do not independently decode YAML or exhaustively retest every Promptkit field combination. -
Add a copyable
examples/render-v0.9-features.shworkflow that uses onlyrenderandinspectoperations to demonstrate prompt-version selection, a direct session ID, reasoning replacement or clearing, a configured custom backend, and both inspection modes without provider access. Keepexamples/config.full.ymlcomplete and keep raw credentials out of every example. -
Review every canonical current-state owner named by the feature roadmap. Remove stale claims that inputs are always required, that every command needs
prompt_dir, or that v0.1.0 semantics still apply. Ensure CLI, API, configuration, operations, internal adapter/source, development, and overview documents link instead of duplicating one another's contracts. Change architecture policy or internal component boundaries only if the implementation actually changed those durable responsibilities. -
Verify that all explicit non-goals remain absent: appended messages, retained prepared handles, HTTP inspection routes, alternative Promptkit source kinds, request output-contract replacement, public provider details, direct catalog imports, raw API keys, and durable workflow state.
-
After every completion criterion below passes, change the feature roadmap's Status from
PlannedtoComplete. Do not mark it complete for a partial implementation.
Final Validation
Run all of the following from the repository root:
gofmt -w $(git ls-files '*.go')
go test ./...
go test -race ./...
go vet ./...
go build ./cmd/scriptorium
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
bash ./examples/render-v0.9-features.sh
git diff --check
Also verify the architecture guard, strict example decoding, documentation links, executable permissions on shell examples, and absence of secrets. Review the suite for redundant upstream-semantic tests and retain only tests that protect Scriptorium mappings, presentation, configuration, concurrency integration, or complete workflows.
Completion Conditions
Every roadmap validation criterion is satisfied on the tagged dependency; all current-state documentation and examples describe implemented behavior; the full application supports every Promptkit v0.9.0 feature expressible in its directory-backed prompt and profile definitions; and the ADR 0004 non-goals remain intact.
Open Questions
None. The external syntax, application mappings, validation ownership, inspection wire shapes, concurrency behavior, testing boundaries, and stage order required for implementation are fixed above.