Add feature roadmap and implementation plan for downstream consumer wishlist items
This commit is contained in:
280
docs/roadmap/prepared-execution.md
Normal file
280
docs/roadmap/prepared-execution.md
Normal file
@@ -0,0 +1,280 @@
|
||||
# Executable Preparation Handles
|
||||
|
||||
**Status:** Accepted.
|
||||
|
||||
## Purpose
|
||||
|
||||
Allow a consumer to prepare one exact Promptkit execution, inspect and retain
|
||||
its credential-redacted public preparation details, and later execute that
|
||||
already-prepared work without resolving or rendering the request again.
|
||||
|
||||
This provides a supported preflight-before-generation boundary for
|
||||
[Weatherreporter](weatherreporter-promptkit-wishlist.md#priority-1-executable-preparation-handles)
|
||||
and removes the duplicate `Prepare`-then-`Run` workaround described by
|
||||
[Notarius](notarius-promptkit-wishlist.md#priority-1-atomic-execution-with-prepared-details).
|
||||
|
||||
## Motivation
|
||||
|
||||
`Engine.Prepare` currently returns the provenance and rendered details that
|
||||
consumers need for debugging, persistence, and preflight checks. `Engine.Run`
|
||||
then performs its own preparation before generation. A consumer that needs
|
||||
both values must therefore prepare the same logical request twice.
|
||||
|
||||
That workaround duplicates source loading, input hashing, schema work, and
|
||||
template rendering. It also permits filesystem-backed prompts, profiles,
|
||||
schemas, or inputs to change between the public preparation and the
|
||||
preparation that actually produces the result.
|
||||
|
||||
Promptkit already owns the complete preparation and execution pipeline. An
|
||||
opt-in prepared-execution handle should expose the missing boundary without
|
||||
putting rendered content into every `RunResult` or moving persistence policy
|
||||
into the library.
|
||||
|
||||
## Consumer Workflow
|
||||
|
||||
The target public workflow is:
|
||||
|
||||
```go
|
||||
prepared, err := engine.PrepareExecution(ctx, request)
|
||||
if err != nil {
|
||||
// Handle preparation failure.
|
||||
return
|
||||
}
|
||||
defer prepared.Discard()
|
||||
|
||||
details := prepared.Details()
|
||||
// Persist or inspect a consumer-selected safe subset of details.
|
||||
|
||||
result, err := engine.RunPrepared(ctx, prepared)
|
||||
```
|
||||
|
||||
The target public surface is:
|
||||
|
||||
```go
|
||||
type PreparedExecution struct {
|
||||
// Opaque Promptkit-owned state.
|
||||
}
|
||||
|
||||
func (e *Engine) PrepareExecution(
|
||||
ctx context.Context,
|
||||
req RunRequest,
|
||||
) (*PreparedExecution, error)
|
||||
|
||||
func (p *PreparedExecution) Details() PreparedRun
|
||||
|
||||
func (p *PreparedExecution) Discard()
|
||||
|
||||
func (e *Engine) RunPrepared(
|
||||
ctx context.Context,
|
||||
prepared *PreparedExecution,
|
||||
) (*RunResult, error)
|
||||
```
|
||||
|
||||
The declarations and GoDoc will own the exact implemented contract. The
|
||||
important public shape is an opaque handle, caller-owned `PreparedRun`
|
||||
details, an explicit discard operation, and execution through the engine that
|
||||
created the handle.
|
||||
|
||||
## Prepared Snapshot
|
||||
|
||||
`PrepareExecution` performs complete preparation without model generation or
|
||||
backend-capacity admission. It applies the same request validation, source
|
||||
precedence, backend and profile resolution, credential requirement checks,
|
||||
output-contract resolution, artifact loading, hashing, schema loading,
|
||||
session resolution, and rendering behavior as `Prepare`.
|
||||
|
||||
A successful handle freezes all source-derived state required for later
|
||||
execution, including:
|
||||
|
||||
- the selected prompt definition, profile, and backend identity;
|
||||
- the complete effective execution target and request-field presence;
|
||||
- rendered messages and effective session ID;
|
||||
- prompt, rendered-prompt, and input hashes;
|
||||
- the effective output contract and provider-facing structured-output
|
||||
constraint; and
|
||||
- private validation state sufficient to validate generated output without
|
||||
reopening schema files or `fs.FS` resources, including required schema
|
||||
references.
|
||||
|
||||
After `PrepareExecution` succeeds, changes to prompt, profile, schema, input,
|
||||
or request-owned data cannot change what `RunPrepared` sends to the model or
|
||||
how it validates the generated output.
|
||||
|
||||
The handle retains an internal snapshot independent from values returned by
|
||||
`Details`. Mutating a returned `PreparedRun`, its maps, slices, messages, or
|
||||
schema values does not affect later execution. Each `Details` call returns a
|
||||
fresh caller-owned copy under the existing `PreparedRun` ownership and stable
|
||||
JSON rules.
|
||||
|
||||
## Handle Lifecycle
|
||||
|
||||
A `PreparedExecution` is:
|
||||
|
||||
- created only by a successful `PrepareExecution` call;
|
||||
- bound to the exact `Engine` that created it;
|
||||
- valid for one `RunPrepared` invocation;
|
||||
- safe for repeated `Details` calls;
|
||||
- intentionally opaque and without a supported JSON representation; and
|
||||
- in-process state rather than a durable or restartable job.
|
||||
|
||||
`RunPrepared` atomically claims a valid handle before beginning the execution
|
||||
attempt. A second or concurrent invocation fails without starting another
|
||||
execution, including when the first invocation ended in cancellation, capacity
|
||||
rejection, generation failure, or operational validation failure. Copies of
|
||||
the public handle share the same one-attempt state and cannot bypass this rule.
|
||||
|
||||
A nil, zero-value, foreign-engine, discarded, already-claimed, or already-used
|
||||
handle is invalid. `RunPrepared` reports these lifecycle errors through the
|
||||
ordinary public invalid-request category. A failed foreign-engine invocation
|
||||
does not consume a handle that remains valid for its owning engine.
|
||||
|
||||
`Discard` idempotently makes an unclaimed handle unavailable for execution and
|
||||
drops Promptkit's references to secret-bearing or execution-only state.
|
||||
`RunPrepared` performs the same cleanup automatically after claiming a handle.
|
||||
Credential-redacted public preparation details remain available after discard,
|
||||
success, or failure so consumers can retain diagnostics. Promptkit does not
|
||||
promise secure erasure of Go string memory.
|
||||
|
||||
Lifecycle transitions are concurrency-safe. When `RunPrepared` and `Discard`
|
||||
race, exactly one claims the ready handle. `Discard` is not an execution
|
||||
cancellation mechanism and does not interrupt an attempt that has already
|
||||
claimed the handle; consumers cancel that attempt through its context.
|
||||
|
||||
## Credentials And Sensitive Data
|
||||
|
||||
`Details` has the same security contract as `PreparedRun`: it can contain
|
||||
rendered messages, schemas, identifiers, and hashes, but never a resolved API
|
||||
key value. Consumers remain responsible for selecting, redacting, storing, and
|
||||
retaining any persisted preparation material.
|
||||
|
||||
A direct `RunRequest.APIKey` is retained only in opaque execution state until
|
||||
the handle is run or discarded. It is never added to details, hashes, JSON,
|
||||
`String`, or `GoString` output.
|
||||
|
||||
An environment-variable name is frozen as part of the effective target, but
|
||||
its credential value is not captured for the lifetime of the handle.
|
||||
`PrepareExecution` applies the existing preparation-time availability check.
|
||||
`RunPrepared` rechecks availability before admission, and the selected model
|
||||
client uses the environment value visible during execution. This preserves
|
||||
current secret ownership and avoids retaining an environment credential while
|
||||
a consumer persists preflight material.
|
||||
|
||||
The opaque handle must not expose retained request data or credentials through
|
||||
default formatting, JSON, or error messages.
|
||||
|
||||
## Admission, Cancellation, And Execution
|
||||
|
||||
`PrepareExecution` never reserves backend admission or an active-generation
|
||||
permit. Its context governs preparation only; cancellation after it returns
|
||||
does not invalidate the handle.
|
||||
|
||||
`RunPrepared` uses its own context for credential revalidation, backend
|
||||
admission, active-generation waiting, model generation, output validation, and
|
||||
any internal repair. Admission occurs when `RunPrepared` begins so a consumer
|
||||
cannot occupy bounded capacity while inspecting or persisting preparation
|
||||
details.
|
||||
|
||||
For a limited backend, the admission lease covers the complete prepared
|
||||
execution attempt after admission: generation, validation, internal repair,
|
||||
and every success or failure exit. Actual generation continues to use the
|
||||
backend's FIFO active-generation permit. Existing capacity error identity,
|
||||
cancellation behavior, and release guarantees remain in force.
|
||||
|
||||
Because a prepared handle is one-attempt, cancellation or capacity rejection
|
||||
does not make it reusable. Retry and backoff policy remains with the consumer,
|
||||
which may create a new prepared handle when another attempt is appropriate.
|
||||
|
||||
## Results And Failures
|
||||
|
||||
On success, `RunPrepared` returns the existing caller-owned `RunResult`. Its
|
||||
source-derived provenance must match `Details`, including prompt identity and
|
||||
hash, rendered-prompt hash, session ID, selected profile and backend,
|
||||
effective target, and input hashes.
|
||||
|
||||
`RunResult.StartTime`, `EndTime`, and `Duration` describe the
|
||||
`RunPrepared` execution attempt. They exclude preparation time and any delay
|
||||
while the consumer retained the handle. Preparation timing remains in
|
||||
`PreparedRun`.
|
||||
|
||||
Preparation failure returns no handle. After successful preparation,
|
||||
`RunPrepared` retains the existing rule that an operational failure returns no
|
||||
partial `RunResult`; the consumer already has independent preparation details.
|
||||
A completed content-validation failure remains a successful result with
|
||||
`ValidationFailed`.
|
||||
|
||||
`PrepareExecution` preserves the public error categories of `Prepare`.
|
||||
`RunPrepared` preserves applicable invalid-request, credential, capacity,
|
||||
generation, validation, collaborator, and cancellation identities without
|
||||
reintroducing source-loading or rendering failures from frozen state.
|
||||
|
||||
## Compatibility And Existing Workflows
|
||||
|
||||
This feature is additive:
|
||||
|
||||
- `Prepare` remains the simple preparation-only operation;
|
||||
- `Run` remains the simple prepare-and-execute operation with its current
|
||||
early-admission and error-ordering behavior;
|
||||
- `PreparedRun` and `RunResult` retain their existing stable JSON
|
||||
representations;
|
||||
- model-client and artifact-reader extension interfaces remain unchanged; and
|
||||
- backend routing, concurrency limits, queue capacities, and provider wire
|
||||
behavior remain unchanged.
|
||||
|
||||
The new workflow may share internal machinery with `Prepare` and `Run`, but it
|
||||
must not change their observable behavior merely to simplify implementation.
|
||||
|
||||
## Documentation
|
||||
|
||||
The completed documentation set has these ownership boundaries:
|
||||
|
||||
- exported declarations and GoDoc own the exact handle, method, lifecycle,
|
||||
ownership, concurrency, credential, error, and cancellation contracts;
|
||||
- the promptkit consumer guide explains when to use `Prepare`, `Run`, or the
|
||||
two-phase prepared-execution workflow; and
|
||||
- internal runner, source-validation, capacity, and model-client documentation
|
||||
describe the implemented collaborator boundaries without duplicating public
|
||||
contracts.
|
||||
|
||||
No release document is part of the feature implementation itself. Release
|
||||
guidance is prepared only when the resulting public API is selected for
|
||||
publication.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
This work does not include:
|
||||
|
||||
- serializable, durable, resumable, or cross-process execution handles;
|
||||
- reuse for multiple consumer-initiated executions;
|
||||
- concurrent execution of one handle;
|
||||
- capacity reservation during preparation;
|
||||
- a background task queue, priorities, worker lifecycle, or job status;
|
||||
- retry, backoff, or provider failover policy;
|
||||
- a separate `RunDetailed` convenience method;
|
||||
- adding preparation details or rendered messages to every `RunResult`;
|
||||
- prompt or profile inspection APIs;
|
||||
- structured capacity or generation errors;
|
||||
- freezing environment-variable credential values for the handle lifetime;
|
||||
- snapshotting provider state or mutable behavior inside an injected
|
||||
`LLMClient`;
|
||||
- allowing target, validation, session, variable, or input overrides after
|
||||
preparation; or
|
||||
- changing existing `Prepare`, `Run`, file-format, provider-request, or stable
|
||||
JSON contracts.
|
||||
|
||||
## Target End State
|
||||
|
||||
After this work:
|
||||
|
||||
- consumers can perform and persist preflight before starting provider work;
|
||||
- one prepared handle executes exactly the source-derived prompt, target,
|
||||
schema, inputs, session, and messages described by its public details;
|
||||
- execution never reloads or rerenders consumer sources;
|
||||
- direct credentials remain confined to opaque, explicitly discardable state;
|
||||
- environment credentials are not retained across the preflight boundary;
|
||||
- backend capacity is reserved only when execution begins;
|
||||
- one handle can start at most one execution attempt, including any internal
|
||||
repair calls owned by that attempt;
|
||||
- preparation details remain available after execution success or failure;
|
||||
- existing simple `Prepare` and `Run` consumers remain unaffected; and
|
||||
- Promptkit continues to own reusable execution mechanics without taking on
|
||||
downstream persistence, redaction, retry, or job-management policy.
|
||||
Reference in New Issue
Block a user