Add a roadmap, implementation plan, and built-in profile defaults for a production-ready public library package
This commit is contained in:
@@ -1,134 +1,176 @@
|
||||
# Library API Roadmap
|
||||
# Library API Production Roadmap
|
||||
|
||||
This roadmap defines the target behavior for making Scriptorium usable as an imported Go library while retaining the current standalone CLI and HTTP application behavior.
|
||||
This roadmap defines the target state for making Scriptorium's public Go package production-ready for downstream applications while preserving the existing CLI and HTTP behavior.
|
||||
|
||||
The implementation plan for this feature lives in `docs/roadmap/implementation.md`.
|
||||
The library remains an additional adapter surface. It should not replace the subprocess, CLI, or HTTP contracts that already exist.
|
||||
|
||||
## Motivation
|
||||
|
||||
Scriptorium is currently optimized for subprocess use by other applications. That contract remains useful because it is language-neutral, operationally simple, and process-isolated.
|
||||
Many Go applications can use Scriptorium more cleanly as an imported package than as a subprocess. A downstream developer should be able to keep prompt assets in standard Scriptorium format, pass application data through a small adapter, and receive a typed response without reimplementing prompt rendering, profile resolution, validation, or OpenAI-compatible request construction.
|
||||
|
||||
For Go callers, an imported library should provide:
|
||||
The core consumer story is:
|
||||
|
||||
- typed requests and results instead of stdout/stderr parsing;
|
||||
- direct `context.Context` cancellation;
|
||||
- lower overhead for repeated calls;
|
||||
- easier test integration through injected clients or fixtures;
|
||||
- direct access to prepared-run data without process management;
|
||||
- fewer integration points where secrets or output metadata can be mishandled.
|
||||
- the downstream app owns one or more `prompt.yml` files in standard Scriptorium format;
|
||||
- those prompts may use inline content, `content_file` references, variables, cache-control markers, and structured-output schemas;
|
||||
- the app may provide its own `profile.yml`, or select a standard built-in/profile-template configuration;
|
||||
- the app supplies an API key as a normal Go value when the selected profile requires one;
|
||||
- the app calls the public Go package to prepare or run the request and receives typed results.
|
||||
|
||||
The library is an additional adapter surface, not a replacement for the CLI or HTTP API.
|
||||
## Current State
|
||||
|
||||
The public package already provides the first library facade:
|
||||
|
||||
- root package import;
|
||||
- typed engine construction;
|
||||
- typed prepare/run requests and results;
|
||||
- file and inline artifact references;
|
||||
- execution overrides;
|
||||
- custom LLM injection for testing or alternate execution;
|
||||
- public error categories that map internal failures to stable caller-facing errors.
|
||||
|
||||
The remaining production-readiness gaps are mostly about consumer ergonomics and asset sourcing:
|
||||
|
||||
- callers are still oriented around filesystem prompt/profile/schema directories;
|
||||
- embedded prompt/profile/schema assets are not a first-class public use case;
|
||||
- standard or built-in profile selection is not yet available;
|
||||
- library credential supply is still tightly coupled to environment-variable lookup rather than direct API-key values;
|
||||
- public documentation and examples need to show the intended downstream app adapter pattern.
|
||||
|
||||
## Target State
|
||||
|
||||
Scriptorium should expose a small public Go API suitable for common embedding use cases:
|
||||
The public package should let a downstream Go application use standard Scriptorium assets without temporary directories, subprocess invocation, or internal package imports.
|
||||
|
||||
- construct an engine from app-level settings such as prompt, profile, and schema directories;
|
||||
- prepare a prompt request without calling an LLM;
|
||||
- run a prompt request and receive a typed result;
|
||||
- pass file and inline artifacts;
|
||||
- apply profile selection, runtime overrides, vars, validation behavior, cache-control behavior, and structured-output behavior consistently with CLI/HTTP;
|
||||
- inject a custom LLM client or HTTP client where needed;
|
||||
- preserve existing CLI and HTTP behavior by continuing to route all entry paths through the same use-case layer.
|
||||
### Prompt Assets
|
||||
|
||||
The public library API should be stable, narrow, and intentionally higher-level than the current `internal/*` package layout.
|
||||
The library should support prompt definitions from:
|
||||
|
||||
## Public Package Policy
|
||||
- existing prompt directories;
|
||||
- a single prompt file;
|
||||
- `fs.FS`, including `embed.FS`.
|
||||
|
||||
The public package should be the module root:
|
||||
Prompt syntax should remain the standard Scriptorium prompt YAML format. `content_file` references should continue to be supported and should resolve relative to the prompt definition's source location within the same asset source.
|
||||
|
||||
```go
|
||||
import "gitea.maximumdirect.net/eric/scriptorium"
|
||||
```
|
||||
The public API should not introduce a separate in-code prompt DSL as the primary path. YAML remains the canonical authoring format so prompts can be shared between CLI, HTTP, subprocess, and library usage.
|
||||
|
||||
Recommended usage shape:
|
||||
### Schema Assets
|
||||
|
||||
```go
|
||||
engine, err := scriptorium.NewEngine(scriptorium.Config{
|
||||
PromptDir: "./prompts",
|
||||
ProfileDir: "./profiles",
|
||||
SchemaDir: "./schemas",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Structured-output schemas should be loadable from the same kinds of sources as prompt definitions:
|
||||
|
||||
prepared, err := engine.Prepare(ctx, scriptorium.RunRequest{
|
||||
PromptID: "generic.markdown_summary",
|
||||
Inputs: map[string]scriptorium.ArtifactRef{
|
||||
"transcript": scriptorium.File("./transcript.md"),
|
||||
},
|
||||
})
|
||||
- existing schema directories;
|
||||
- a single schema file where appropriate;
|
||||
- `fs.FS`, including `embed.FS`.
|
||||
|
||||
result, err := engine.Run(ctx, scriptorium.RunRequest{
|
||||
PromptID: "generic.markdown_summary",
|
||||
Inputs: map[string]scriptorium.ArtifactRef{
|
||||
"transcript": scriptorium.File("./transcript.md"),
|
||||
},
|
||||
})
|
||||
```
|
||||
Schema references should retain the existing prompt-format semantics. A schema referenced by a prompt should resolve through the configured schema source, not through ad hoc caller code.
|
||||
|
||||
## Policy Decisions
|
||||
### Profile Assets
|
||||
|
||||
### Public Package Scope
|
||||
The library should support both custom and standard profile configuration:
|
||||
|
||||
Expose a narrow root facade package and keep existing `internal/*` packages internal.
|
||||
- existing profile directories;
|
||||
- a single profile file;
|
||||
- `fs.FS`, including `embed.FS`;
|
||||
- direct public profile values for applications that already have profile configuration in memory;
|
||||
- built-in/profile-template helpers for common OpenAI-compatible targets.
|
||||
|
||||
Reasoning:
|
||||
Custom profiles and built-in/template profiles should flow through the same internal profile resolution and request-construction path. The built-in path should not become a separate execution mode.
|
||||
|
||||
This gives callers the workflow they need without freezing the internal architecture as public API. It also preserves the current package-boundary policy and keeps future refactoring possible.
|
||||
### Built-In Profile Templates
|
||||
|
||||
### Public Type Strategy
|
||||
Built-in support should favor stable profile templates over a large registry of fixed model IDs.
|
||||
|
||||
Define public facade types and map them to internal domain types.
|
||||
For example, the public package should make it easy to construct or select an OpenAI-compatible profile by supplying the durable parts of the profile:
|
||||
|
||||
Reasoning:
|
||||
- profile ID or name;
|
||||
- base URL;
|
||||
- model;
|
||||
- whether the profile requires an API key;
|
||||
- default numeric parameters where desired;
|
||||
- structured-output and extra-parameter behavior consistent with normal profiles.
|
||||
|
||||
Public types can be designed around caller needs and long-term stability. Internal types can continue to evolve with implementation details such as adapter metadata, validation internals, and provider-specific behavior.
|
||||
The package may include a small set of named helpers for common OpenAI-compatible services, but those helpers should avoid hard-coding a broad and fast-changing list of model names.
|
||||
|
||||
### CLI And HTTP Reuse
|
||||
### Credentials
|
||||
|
||||
Keep CLI and HTTP on current internal wiring for the initial library release. Consider migrating them to the public facade only after the facade proves stable.
|
||||
The public package must keep raw API keys out of prompt/profile YAML, prepared-run output, run results, logs, and examples.
|
||||
|
||||
Reasoning:
|
||||
For the public library API, the single supported credential-supply method should be a direct API-key value passed by the consuming Go application. The consuming application is responsible for loading and managing its own secrets before calling Scriptorium.
|
||||
|
||||
This minimizes risk to the existing subprocess and HTTP contracts while adding the new API. It also avoids forcing the first public facade to satisfy every adapter edge case immediately.
|
||||
This may be exposed as a field such as `Config.APIKey`, an option such as `WithAPIKey`, or an equivalent request/engine-level value that is easy to pass through an application adapter. The exact API should avoid accidental serialization in prepared output, run results, logs, and examples.
|
||||
|
||||
### Error Surface
|
||||
The public package should not encourage raw API-key storage in prompt/profile YAML. Existing CLI behavior may continue to use environment-variable references for compatibility, but the production library path should not introduce a separate credential resolver or secret-manager abstraction.
|
||||
|
||||
Expose public sentinel errors or typed error categories and map internal errors to them while preserving wrapped context.
|
||||
### Public API Shape
|
||||
|
||||
Reasoning:
|
||||
The public API should remain narrow, idiomatic, and stable. Recommended additions include:
|
||||
|
||||
Library callers need stable, idiomatic error checks. Mapping internal errors avoids exposing internal package paths as public compatibility promises.
|
||||
- engine options for prompt/profile/schema directories;
|
||||
- engine options for prompt/profile/schema `fs.FS` sources;
|
||||
- engine options for single prompt/profile/schema files where useful;
|
||||
- public profile/template constructors that map to internal profile definitions;
|
||||
- a direct API-key value for profiles that require authentication;
|
||||
- examples showing `embed.FS`, custom profile files, template profile selection, and fake LLM testing.
|
||||
|
||||
The public package should continue to expose facade types rather than exporting internal package types. Internal package layout should remain free to evolve.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- Public facade package for library consumers.
|
||||
- Public request, result, prepared-run, artifact reference, execution override, validation, and config types.
|
||||
- Public constructors for common file and inline input references.
|
||||
- Public engine methods for `Prepare` and `Run`.
|
||||
- Optional dependency injection for LLM behavior and HTTP behavior.
|
||||
- Stable error behavior suitable for `errors.Is` and `errors.As`.
|
||||
- Tests proving public API behavior matches CLI/use-case behavior.
|
||||
- Documentation and examples for library usage after implementation.
|
||||
- first-class `fs.FS` support for public library prompt, profile, and schema sources;
|
||||
- ergonomic single-file asset options where they reduce caller boilerplate;
|
||||
- built-in/profile-template helpers for common OpenAI-compatible usage;
|
||||
- in-memory public profile values where appropriate;
|
||||
- direct API-key value support for the public library path;
|
||||
- consumer-facing examples under `examples/`;
|
||||
- consumer package documentation under `docs/consumers/` once behavior is implemented;
|
||||
- tests proving library behavior matches existing CLI/use-case behavior.
|
||||
|
||||
Out of scope for the first library release:
|
||||
Out of scope:
|
||||
|
||||
- Making every `internal/*` package public.
|
||||
- Replacing or rewiring the CLI or HTTP adapters.
|
||||
- Adding a durable run store or workflow engine.
|
||||
- Adding broad provider-specific SDK surfaces.
|
||||
- Adding non-Go language bindings.
|
||||
- Adding global mutable configuration.
|
||||
- changing standard prompt, profile, or schema file formats;
|
||||
- exposing internal packages as public API;
|
||||
- replacing or removing CLI, HTTP, or subprocess support;
|
||||
- adding a multi-step workflow engine;
|
||||
- adding non-Go bindings;
|
||||
- maintaining a comprehensive provider/model catalog;
|
||||
- accepting raw API keys in serialized YAML/JSON configuration;
|
||||
- adding a credential resolver or secret-manager abstraction.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- A Go caller can import the root module and run a prompt without invoking a subprocess.
|
||||
- A Go caller can prepare a prompt without invoking an LLM.
|
||||
- Public library behavior matches current CLI/HTTP use-case semantics for prompt/profile loading, artifact reading, rendering, validation, and model invocation.
|
||||
- A Go caller can import the root package and run a standard Scriptorium prompt without invoking a subprocess.
|
||||
- A Go caller can use prompt definitions from `embed.FS`, including prompts with `content_file` references.
|
||||
- A Go caller can use structured-output schemas from `embed.FS` or filesystem sources.
|
||||
- A Go caller can provide a custom profile from filesystem, `fs.FS`, or public in-memory profile values.
|
||||
- A Go caller can select a standard OpenAI-compatible profile template without writing a full profile file.
|
||||
- A Go caller can supply an API key as a normal Go value without raw secrets appearing in serialized config, prepared output, or results.
|
||||
- Public library behavior remains consistent with CLI/HTTP semantics for rendering, validation, profile resolution, runtime overrides, structured output, cache control, and LLM invocation.
|
||||
- Existing CLI and HTTP behavior remains unchanged.
|
||||
- Library tests use injected/fake LLM behavior and do not require real provider credentials.
|
||||
- Public documentation is concise and limited to implemented behavior once code exists.
|
||||
- Library tests use fake or local LLM boundaries and do not require real provider credentials.
|
||||
- Public docs outside `docs/roadmap/` describe only implemented behavior after the feature is built.
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### Asset Source API
|
||||
|
||||
Add first-class `fs.FS` options for prompt, profile, and schema sources while keeping existing directory-based configuration. Also add single-file convenience options where they remove meaningful caller boilerplate. Resolve `content_file` references relative to the prompt file's location inside the same source.
|
||||
|
||||
Reasoning:
|
||||
|
||||
This is the most idiomatic path for production Go libraries because it supports `embed.FS`, `os.DirFS`, tests, and in-memory fixture files through the same abstraction. It also avoids requiring downstream applications to unpack embedded assets into temporary directories.
|
||||
|
||||
### Built-In Profile Strategy
|
||||
|
||||
Provide stable profile-template helpers for OpenAI-compatible endpoints rather than a broad registry of fixed provider/model profiles. Let callers choose the model and endpoint where those values are service-specific or fast-changing.
|
||||
|
||||
Reasoning:
|
||||
|
||||
Endpoint shape and credential mechanics are relatively stable; model catalogs change frequently. Templates give consumers a short, correct path without making Scriptorium responsible for tracking every provider's model list.
|
||||
|
||||
### In-Memory Profile Values
|
||||
|
||||
Expose a small public profile facade type for in-memory profile configuration and map it to internal profile definitions. Keep it intentionally aligned with the existing profile YAML contract.
|
||||
|
||||
Reasoning:
|
||||
|
||||
Many applications already hold configuration in typed structs and should not need to generate YAML files just to call Scriptorium. A public facade keeps internal types private while making the library practical for production use.
|
||||
|
||||
Reference in New Issue
Block a user