# 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.