# Internal Runner ## Purpose This document describes Promptkit's implemented internal orchestration. The [architecture policy](../policy/architecture.md) owns dependency and consumer boundaries. The [source and validation document](sources.md) owns configuration, artifact, rendering, and validation behavior, while the [model-client document](llm.md) owns generation behavior and failure categories. The runner remains under `internal/usecase` and is assembled by `internal/engine`. Its concrete type is not part of the public API. The [framework format reference](../formats.md) owns prompt, profile, schema, and override semantics consumed by the runner. ## Collaborators `Runner` coordinates narrow internal interfaces for captured prompt definitions and resolved profiles, artifacts, rendering, model generation, and validation. The internal engine supplies one engine-local run admitter and a generation coordinator using the same capacity manager. Validation plans and provider-facing schema metadata come from the validator's preparation interface. The internal engine supplies one default output repairer through the explicit runner constructor, using the same coordinator as initial generation. The no-repair runner constructor remains available for focused internal callers and tests. Each invocation carries its state in request, prepared-run, and result values. The runner has no durable run or session store. ## Shared Prompt Selection Ordinary preparation selects the full prompt definition and construction-time hash. It retains its early request-ID check before direct-session normalization. Referenced message content and static template syntax were captured when the engine was built. `InspectPrompt` selects captured metadata through an optional repository capability, falling back to the full definition and existing hash resolution for internal repositories without it. It returns owned metadata without rendering templates or touching profile, artifact, schema, renderer, validator, admission, or model collaborators. The root [`Engine.InspectPrompt`](../../engine.go) GoDoc owns the public operation's exact contract. `InspectRequirements` uses the same metadata selection to resolve the request-profile/default identity, then obtains the exact captured requirement snapshot by the selected prompt ID and version. The analyzer receives only those identity fields rather than an execution definition. It analyzes the snapshot with the supplied caller variables and normalized direct-session bypass; it does not resolve target readiness or enter the preparation pipeline. Missing snapshot capability is a prompt-load failure rather than a source fallback. The root [`Engine.InspectRequirements`](../../requirements.go) GoDoc owns the public operation's exact contract. ## Shared Profile Selection The runner uses one profile-selection and target-resolution boundary for ordinary preparation and exact profile inspection. Preparation first selects a request profile or a prompt default; inspection begins with its required explicit profile ID. Both use the captured, presence-aware target constructed from the final effective profile and backend configuration. Request overrides apply afterward; preparation checks endpoint and model completeness only after those overrides. Inspection can report a partial profile without requiring a runnable target. Neither path checks credential availability or performs prompt, artifact, schema, rendering, admission, or model-client work. The root [`Engine.InspectProfile`](../../engine.go) GoDoc owns the public operation's exact contract. ## Shared Preparation Pipeline `Prepare` and `Run` share one private preparation pipeline split at the point where a run can be assigned to its selected backend pool. The resolution phase performs only the work needed to validate routing and admission: 1. validate the required prompt selection and normalize any direct session ID; 2. select the captured prompt definition and hash; 3. select the request profile or the prompt's default profile; 4. retain the profile's selected backend identity, when present; 5. apply explicit request overrides to its captured backend-completed target; 6. validate endpoint, model, numeric overrides, and credential requirements; 7. resolve and validate the effective output contract without loading its schema; and 8. retain the definition, source identities, effective settings, output contract, and preparation start time in invocation-local state. The completion phase consumes that state without reloading the prompt, profile, or backend: 1. create one operation-local validation plan and derive structured-output schema metadata from it when required; 2. load and hash input artifacts; 3. render messages and the prompt-defined session; 4. apply any direct session ID, then append already-normalized request messages after the rendered definition messages; 5. hash the effective rendered prompt; and 6. construct the prepared value and preparation timing. `Prepare` runs both phases consecutively and never performs capacity admission. `Run` performs backend admission between the phases. This structure preserves one execution-precedence and error-ordering implementation while allowing a full backend pool to reject work before expensive schema, artifact, and rendering operations. `Prepare` discards the plan after returning its public metadata. `Run` retains the plan through initial and repaired-output validation and discards it when the operation ends. Prepared execution transfers the completed preparation's owned message, input-hash, and copied-schema values into its private payload, while deep-copying mutable provider extras and creating an independent details snapshot. Claim completion or discard clears the execution payload without removing the details snapshot. The root facade's `Engine.ValidateRequest` uses the same complete preparation path as `Prepare` and discards the resulting internal prepared value without converting it into a public snapshot. It does not add a runner operation or validator component; request conversion and public error mapping remain at the facade boundary. Pointer-based numeric overrides preserve an explicit zero. Invalid negative or out-of-range values fail as invalid requests. Endpoint overrides do not change the selected backend identity. Non-empty extra-parameter maps replace whole lower-precedence maps. A direct API key takes precedence over environment lookup; otherwise request, explicit profile, and backend environment-variable names apply in that order. A profile requiring a direct key suppresses an inherited backend environment name unless the request supplies its own name. Secret values remain excluded from serialized metadata. Reasoning overrides are tri-state: nil inherits the profile, a pointer to a nonblank string trims and replaces it, and a pointer to a blank string clears it. A nonblank direct session is normalized before prompt selection, bypasses the prompt session template, and is applied after ordinary message rendering. A blank direct value retains prompt-template behavior. The runner clears the template only on a value copy of the definition, so the definition hash always describes captured effective content while the rendered-prompt hash includes the effective direct or rendered session and the complete effective message sequence. The rendered-prompt hash uses a versioned, length-framed SHA-256 encoding of that session, every role and content value, and cache-control presence and values; its hexadecimal value is opaque. The configuration snapshot is read-only after engine construction. Concurrent `Prepare` and `Run` calls copy their resolved targets and keep all invocation state local. ## Run Flow `Run` records its start time, performs the shared resolution phase, and asks its `RunAdmitter` to reserve capacity for the effective backend ID. A nil admitter is an internal unlimited fallback. After successful admission, `Run` immediately defers the returned release function, performs the completion phase, makes one initial generation call, builds the named output artifact, and validates that artifact with the plan compiled during completion. Invalid generated content remains a validation result; an inability to generate or perform validation is an operational error. When the public facade installs an invocation-local observation recorder, ordinary `Run` reuses its run ID and carries it to the generation coordinator through context. Prompt and profile identities are enriched only after their respective resolutions succeed, before admission. The facade starts observation before request conversion and emits its terminal event after public error mapping and after the runner's deferred admission release. Preparation and prepared execution follows the same terminal mapping after a successful claim; invalid or discarded handles do not acquire an observer. Validation preparation and execution honor cancellation at every Promptkit-controlled boundary and do not publish a partial plan or result. Schema reads are bounded and context-checked between chunks; JSON decoding, schema compilation, and schema execution are checked immediately before and after their synchronous calls. Promptkit does not move arbitrary filesystem or JSON Schema work to background goroutines, so an already-blocked dependency method must return before cancellation can take precedence over its outcome. The admission lease covers completion-phase preparation, initial generation, validation, every repair, and every exit. It bounds accepted work without serializing preparation or validation behind the active-generation limit. The coordinator separately acquires a FIFO active permit only around each actual model attempt. It releases the permit before a transient retry wait. After a failed `basic`, JSON, or JSON Schema validation with a positive frozen budget, the installed repairer can make a bounded corrective call. Each request starts with a fresh copy of the complete effective message sequence (the configured prefix followed by the request suffix), includes only the latest nonempty candidate as an assistant message, and appends one corrective user message. Empty candidates omit that assistant message. The correction carries validation diagnostics as JSON data bounded to 64 KiB; the full diagnostics remain in the validation result. Repair receives the effective execution target, explicit numeric-presence bits, credential, backend identity, session ID, structured-output specification, and frozen transient retry budget. The same request constructor supplies those common fields to initial and repair generation. The default repairer uses the same coordinator as initial generation, so each repair reacquires the selected backend's active permit while remaining inside its original admission lease. Repair never performs a second bounded admission, and repaired outputs use the operation's existing validation plan. The runner stops at the first valid candidate, sums completed generation usage, reports calls actually started, and returns the final failed validation result on exhaustion. A repair generation failure follows the ordinary generation-error category rather than becoming a validation error. A successful result includes the output artifact and raw output, validation state, effective session ID, prompt and rendered-prompt hashes, selected profile and backend, effective settings, input hashes, token usage, a generated run identifier, and UTC timing. The same effective session reaches initial generation and any repair attempt through the rendered prompt. The same effective target and presence metadata, including backend identity and direct credential during execution, reaches generation and every repair attempt. Result usage is the field-wise sum of all five usage values from the initial response and every completed repair response. Final raw output, artifact, and validation state still come from the last candidate. A repair error returns no partial run result or partial result usage, although the invocation-local observer retains completed-attempt usage and reports it in the terminal event. ## Failure Categories Package errors distinguish invalid requests, required profile selection, credential failures, and prompt, profile, artifact, rendering, generation, and validation failures. Wrapping preserves the package identities mapped by the public facade and retains collaborator identities where they are part of the internal contract. Admission capacity exhaustion retains the internal capacity identity. At the use-case boundary, the runner attaches the selected backend ID in an internal typed error, and the root facade copies that value into the public [`CapacityError`](../../capacity_error.go) without parsing diagnostic text. It is not recategorized as an invalid request or generation failure, and no partial result is returned. A context already done at admission retains its context identity directly. Cancellation while waiting for an active generation permit prevents client invocation when it wins the grant race; the model-client boundary then preserves the context error through the generation-failure category. Deferred release restores the admission lease on preparation, generation, validation, repair, and cancellation failures. Other context cancellation propagates through the invoked collaborator and is classified by the owning operation. In particular, cancellation observed by validation retains the context identity through the validation error category. An overlong direct session is an invalid request before prompt selection, while an invalid or overlong prompt session template remains a prompt-render failure. An unknown selected backend, or a selected backend with no configured resolver, is classified as a profile-load failure. ## Test Ownership And Changes The [runner tests](../../internal/usecase/runner_test.go) own preparation order, selection and override precedence, the two-phase boundary, early admission, lease lifetime and release, direct-session resolution, schema-before-generation behavior, hashing, generation and validation outcomes, backend propagation, bounded repair progression, initial/repair request parity, cumulative usage, shared initial/repair capacity, credentials and redaction, error categories, artifact metadata, and timing. The [capacity subsystem document](capacity.md) identifies the focused pool and waiter tests. The [generation document](generation.md) owns attempt and retry coordination tests. Changes to orchestration should continue to use the existing package interfaces, keep request state local to an invocation, and preserve the shared resolution and completion pipeline. Source, renderer, validator, or model-client contract changes belong first in their owning package and document.