281 lines
12 KiB
Markdown
281 lines
12 KiB
Markdown
# Executable Preparation Handles
|
|
|
|
**Status:** Complete.
|
|
|
|
## 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.
|