# Package `promptkit` ## Purpose This guide helps Go consumers assemble Promptkit and choose the main preparation or execution workflow. The declarations and GoDoc in the [root package](../../doc.go) own exact field, option, serialization, concurrency, ownership, failure, and cancellation semantics. The [framework format reference](../formats.md) owns prompt, profile, and schema file contracts. Import the package as: ```go import "gitea.maximumdirect.net/eric/promptkit" ``` The following Go fragments are illustrative and omit surrounding package, import, and error-handling code. Use the maintained examples for complete programs. ## Go Compatibility The `go` directive in the selected release's [module metadata](../../go.mod) declares its minimum supported consumer Go version. Maintainers build and validate releases with the locally selected Go patch release; the minimum does not simply track that patch version. Successful release validation on the selected patch is sufficient to presume support across its Go series, subject to the declared minimum and concrete evidence of incompatibility. Specific compatibility concerns are investigated as they arise. Minor releases may raise the minimum; patch releases preserve it. Release notes explain increases and any required consumer action. See the [release policy](../policy/release.md#consumer-minimum-and-release-toolchain). ## Construct An Engine Create an engine with [`NewEngine`](../../engine.go). Select each directory explicitly and place its inputs in an ordered ConfigKit underlay. A group strictly composes its files; later groups can overlay earlier ones: ```go promptInputs, err := promptkit.DirectoryInputs("prompts") if err != nil { return err } profileInputs, err := promptkit.DirectoryInputs("profiles") if err != nil { return err } engine, err := promptkit.NewEngine(promptkit.Config{ Sources: promptkit.ConfigurationSources{Underlays: []configkit.Underlay{ {Inputs: promptInputs}, {Inputs: profileInputs}, }}, SchemaDir: "schemas", }) ``` For a single highest-priority document, set `Sources.Main` to a `*configkit.Input`; omit it for ordinary layered construction. A single file can use `configkit.File` directly. `ProfileSource`, `BackendSource`, and raw `configkit.Mapping` supply programmatic definitions. Schema and runtime collaborator options remain available. Construction validates the final effective configuration and captures referenced prompt content; construct a new engine to observe configuration edits. See the [constructor GoDoc](../../engine.go) for the exact public contract and the [framework format reference](../formats.md) for definition fields. ## Supply Embedded Application Defaults Use `FilesystemInputs` for embedded defaults and `DirectoryInputs` for an explicitly chosen operator directory. A later programmatic group can override both. No directory name has built-in precedence: ```go defaults, err := promptkit.FilesystemInputs("embedded defaults", applicationFS, "config") if err != nil { return err } operator, err := promptkit.DirectoryInputs(operatorDirectory) if err != nil { return err } profile := promptkit.ProfileSource("application choices", map[string]promptkit.ProfileConfig{ "summary": {Model: &selectedModel}, }) engine, err := promptkit.NewEngine(promptkit.Config{Sources: promptkit.ConfigurationSources{ Underlays: []configkit.Underlay{ {Inputs: defaults}, {Inputs: operator}, {Inputs: []configkit.Input{{Source: profile}}}, }, }}) ``` Only add the operator group when the application selects that directory; an explicitly selected missing or unreadable root is an error. Each helper finds `.yaml` and `.yml` files recursively and disables imports by default. The caller may enable imports on individual inputs. A nil `Sources.Main` is an empty main source, not another directory lookup. For a programmatic document with explicit null or a prompt definition, use a labeled mapping as an input. A mapping owns a snapshot of supported values: ```go document := configkit.Mapping("application configuration", map[string]any{ "profiles": map[string]any{ "local-summary": map[string]any{ "backend": "local", "model": "example-model", "temperature": nil, // Suppress an inherited provider control. }, }, }) input := configkit.Input{Source: document} // Add input to a later underlay, or set Sources.Main = &input. ``` `ProfileSource` and `BackendSource` are smaller typed conveniences when null is not needed. A caller underlay can overlay catalog backend fields just as it can overlay profiles; there is no registration-only backend namespace. When configuration or message files change, build and validate a new engine, then switch the application to that engine for subsequent work. Existing engines and prepared handles retain their captured definitions; schema and request artifact resources still follow their runtime lifecycle. Inspection does not watch or reload sources automatically. ## Discover Available Profiles And Prompts Use [`Engine.ListProfiles`](../../listing.go) and [`Engine.ListPrompts`](../../listing.go) to discover exact identities from the engine's final captured configuration, then inspect a selected prompt: ```go profiles, err := engine.ListProfiles(ctx) if err != nil { return err } if len(profiles) > 0 { _, err := engine.InspectProfile(ctx, profiles[0]) if err != nil { return err } } prompts, err := engine.ListPrompts(ctx) if err != nil { return err } if len(prompts) > 0 { selected := prompts[0] inspection, err := engine.InspectPrompt(ctx, selected.ID, selected.Version) if err != nil { return err } _ = inspection } ``` These lists describe effective membership, not ConfigKit contribution history, and do not promise that a profile is ready to execute. Use inspection for details and preparation to resolve request resources. The linked method GoDoc owns ordering, ownership, concurrency, and error behavior; identifiers may be sensitive application data. ## Inspect The Captured Configuration Use [`Engine.InspectConfiguration`](../../configuration_inspection.go) for an operator-selected report of effective configuration, source descriptions, diagnostics, and path history. For a literal ID containing a slash, construct the semantic path with `configkit.Keys`: ```go view, err := engine.InspectConfiguration() if err != nil { return err } path := configkit.Keys("profiles", "team/summary", "model") explanation, found := view.Explain(path) if found && explanation.Effective.Redacted { // Apply the application's disclosure policy before sharing the report. } ``` `WriteEffective` and `WriteExplanation` produce ConfigKit-redacted reports; the application decides whether those reports may be disclosed. Credential- shaped provider keys are redacted, but arbitrary prompt and provider content is not necessarily safe to share. Inspection uses the engine's captured result and does not reopen sources or resolve credentials. `InspectProfile` instead shows backend-completed execution settings for one profile, and `Prepare` or `PreparedExecution.Details` shows request-specific rendered messages, inputs, and validation resources. None of those views is a substitute for the others. To review changes between two independently captured configurations, compare their inspection views: ```go earlier, err := earlierEngine.InspectConfiguration() if err != nil { return err } later, err := candidateEngine.InspectConfiguration() if err != nil { return err } differences, err := earlier.Diff(later) if err != nil { return err } for _, difference := range differences { if difference.HiddenChanged { // Report a protected change without inferring its value. } if difference.Left.Redacted || difference.Right.Redacted { // Apply the application's disclosure policy before presenting snapshots. } } ``` See the [`ConfigurationInspection.Diff` GoDoc](../../configuration_inspection.go) and ConfigKit's [v0.7.0 effective-configuration diff guidance](https://gitea.maximumdirect.net/eric/ConfigKit/src/tag/v0.7.0/docs/config.md#effective-configuration-diffs) for the exact comparison contract. The operation compares only frozen effective configuration values: it does not reread external files or compare their contents, resolved credential contents, later execution preparation, or request-specific overrides. An empty diff is not proof of equivalent execution. Records can contain ordinary application data, so use the application's disclosure policy when presenting them. ## Run Explicit Smoke Diagnostics [`Engine.SmokeTest`](../../smoke_diagnostics.go) runs one explicit diagnostic using a target resolved from the engine's captured configuration. It does not execute the selected application prompt or its inputs, session, artifacts, or output schema. Unlike the offline examples and default test suite, calling it can contact the selected provider and use configured credentials. It is a consumer operation, not an automatic readiness verdict or a release check. For example, a caller can request the built-in text inference check and treat its optional fact as an observation rather than assuming that a nil Go error means success: ```go report, err := engine.SmokeTest(ctx, promptkit.SmokeTestRequest{ Kind: promptkit.SmokeTestTextInference, ProfileID: profileID, APIKey: apiKey, }) if err != nil { if report != nil { retainDisclosureSafeSummary(report) } return err } if report != nil { matched := report.Facts.ExpectedTextMatched switch { case matched == nil: // The comparison was not evaluated or is unknown/inapplicable. case *matched: // The private expected-text comparison matched. default: // It did not match; the operation may still have concluded normally. } } ``` A nil Go error means the diagnostic concluded, not that its finding was positive. Fact pointers are tri-state: nil means unknown, not evaluated, or inapplicable. Reports contain factual observations and effective settings, not generated text or provider bodies; identifiers and settings can still be sensitive. The [`SmokeTest` GoDoc](../../smoke_diagnostics.go) owns selection precedence, exact fields, timeout and retry behavior, supported transports, and disclosure details. Caller cancellation can return both an error and a partial report. Preserve the context error identity and inspect any available observations before returning: ```go report, err := engine.SmokeTest(ctx, request) if report != nil { retainDisclosureSafeSummary(report) } if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { // The report may describe work completed before interruption. } return err } ``` ## Inspect A Prompt Before Preparation Use [`Engine.InspectPrompt`](../../engine.go) to check one configured prompt's declared inputs and output workflow without creating placeholder inputs or resolving a profile: ```go inspection, err := engine.InspectPrompt(ctx, "meeting.summary", "") if err != nil { return err } for _, input := range inspection.Inputs { // Compare the declared input with application configuration. } ``` Use this configuration-time boundary when the application needs only the declared prompt interface. Use `InspectProfile` separately when it must also check a configured profile. Use `Prepare` when it needs inputs, schemas, or rendered messages, and use prepared execution when that work must remain tied to later execution. The method's [GoDoc](../../engine.go) owns exact fields, hash, ownership, and error semantics. ## Inspect Requirements Before Preparation Use [`Engine.InspectRequirements`](../../requirements.go) when the application needs the prompt's recognizable input and caller-variable dependencies for a particular context before it has input artifacts. Unlike `InspectPrompt`, it also walks captured template syntax and selects a profile identity; unlike `Prepare`, it does not load artifacts or render messages. ```go requirements, err := engine.InspectRequirements(ctx, promptkit.RequirementInspectionRequest{ PromptID: "meeting.summary", Vars: map[string]string{"audience": "project leads"}, }) if err != nil { return err } for _, input := range requirements.Inputs { if input.Kind == promptkit.RequirementRequired { // Plan to bind this input before preparation. } if input.Kind == promptkit.RequirementOptional && input.Explanation != "" { // A recognizable reference may occur only on a known skipped path. } } for _, variable := range requirements.Variables { if variable.Kind == promptkit.RequirementRequired && !variable.Supplied { // The key is required even though this inspection did not receive it. } } if !requirements.Complete { // Preserve the partial inventory and account for its discovery issues. } ``` Optional entries can include recognizable inputs and variables found only in inactive branches. A missing required variable is reported separately from discovery completeness: missing a value used only for direct text substitution does not by itself make the inventory incomplete. Conversely, an unresolved dependency-bearing branch can make discovery incomplete while retaining known conditional entries. Do not treat an empty partial inventory as proof that no input bindings are needed. See the method's [GoDoc](../../requirements.go) for the exact classifications, supported syntax, ownership, and error contract. ## Validate A Concrete Request Use [`Engine.ValidateRequest`](../../engine.go) when the application has a complete `RunRequest` and needs to know whether that concrete invocation passes preparation: ```go request := promptkit.RunRequest{ PromptID: "meeting.summary", Vars: map[string]string{"audience": "project leads"}, Inputs: map[string]promptkit.ArtifactRef{ "note": promptkit.Inline("Synthetic meeting notes"), }, } if err := engine.ValidateRequest(ctx, request); err != nil { return err } ``` Unlike `InspectRequirements`, this checks supplied values, the selected template branches, and request bindings through the existing preparation path. It checks only this invocation, does not aggregate errors, and loads every supplied input according to ordinary preparation behavior, even if a branch does not use it. It makes no model call and returns no prepared snapshot, but artifact and schema resource I/O and its time and memory costs remain. Success does not certify provider acceptance or later execution success. See the method's GoDoc for its exact error and context behavior. Scriptorium can map the returned error to its own diagnostics and exit status. Its `validate` and `run` commands should construct equivalent requests for equivalent caller inputs so their preparation behavior agrees. Separate subprocess calls prepare independently; an opaque `PrepareExecution` handle cannot be transferred from `validate` to `run`. Use `PrepareExecution` when one process and engine must execute the exact prepared snapshot. ## Prepare Without Model Execution [`Engine.Prepare`](../../engine.go) resolves the selected prompt and profile, loads inputs and any structured-output schema, and renders messages without calling a model client. Choose it when the prepared value is the final inspection or persistence result and no later execution must be tied to that exact snapshot: ```go prepared, err := engine.Prepare(ctx, promptkit.RunRequest{ PromptID: "meeting.summary", Inputs: map[string]promptkit.ArtifactRef{ "note": promptkit.Inline("Synthetic meeting notes"), }, }) ``` The maintained [offline preparation example](../../examples/go-library/prepare/main.go) shows a complete runnable setup with a prompt file, in-memory profile, and inline input. It demonstrates validation and preparation with the same request to make the two operations concrete; this performs preparation twice and is illustrative, not a requirement to validate before `Prepare` or `Run`. Exact request requirements and prepared-result fields belong to the [`RunRequest` and `PreparedRun` GoDoc](../../types.go). ## Prepare Now And Execute The Same Snapshot Later Use [`Engine.PrepareExecution`](../../engine.go) when an application must inspect or persist preflight details before deciding whether to start model work, while ensuring that later execution uses those exact rendered messages, inputs, target settings, and validation resources: ```go preparedExecution, err := engine.PrepareExecution(ctx, promptkit.RunRequest{ PromptID: "meeting.summary", Inputs: map[string]promptkit.ArtifactRef{ "note": promptkit.Inline("Synthetic meeting notes"), }, }) if err != nil { return err } defer preparedExecution.Discard() details := preparedExecution.Details() // Inspect or persist an application-selected safe subset of details. result, err := engine.RunPrepared(ctx, preparedExecution) ``` Preparation does not call the model or reserve backend capacity. `RunPrepared` executes from the retained snapshot rather than reloading consumer sources. The handle is opaque in-process state, while `Details` contains rendered content and remains subject to the application's data handling policy. The [`PreparedExecution` and method GoDoc](../../prepared_execution.go) and [engine operation GoDoc](../../engine.go) own exact lifecycle, engine-binding, credential, cancellation, timing, and error semantics. If the request supplied `Observer`, successful preparation retains that function for `RunPrepared`; preparation itself emits nothing. Observation starts only when the owning engine successfully claims the handle. Reused, discarded, foreign, and invalid handles emit nothing. `Discard` clears the retained callback without invoking it. The function's closure is retained by identity, so synchronize any shared state it uses. Prepared start events can include identities already resolved in the frozen snapshot. ## Execute And Validate [`Engine.Run`](../../engine.go) performs the same preparation, invokes the configured model client, classifies the generated artifact, and validates the content in one call. Choose it when the application does not need a preflight boundary tied to the eventual execution. A completed content check may return `ValidationFailed` in the result; an operational inability to validate returns an error. The maintained [offline execution example](../../examples/go-library/run/main.go) injects a deterministic model client and exercises `Run` without credentials, network access, or paid calls. It is intentionally separate from the preparation example so each workflow and its small prompt fixture can be copied and run on its own. Use the [`RunResult` and `ValidationResult` GoDoc](../../types.go) for the returned data and the `Engine.Run` GoDoc for failure and cancellation semantics. The [OpenAI-compatible integration contract](../integrations/openai-compatible-chat.md) owns the built-in client's outbound HTTP behavior. ### Observe An Ordinary Run Set `RunRequest.Observer` to receive synchronous metadata events from `Run`: ```go request.Observer = func(event promptkit.ExecutionEvent) { // Translate selected metadata into an application-owned record. } result, err := engine.Run(ctx, request) ``` An ordinary run emits `started` before request conversion, followed by any attempt and retry-delay completions, then one `finished` event on a normal return. `Prepare` and `PrepareExecution` emit no events. A start event has no resolved prompt, profile, or model; later events gain identities only as selection succeeds. The terminal event has dispatched-attempt, transient-retry, repair-call, duration, and presence-aware usage totals. Its usage is an aggregate, not an additional billable attempt. A known zero differs from an unknown count, and incomplete totals must not be treated as complete cost accounting. A failed operation still returns no partial `RunResult`. An injected client that leaves `UsagePresence` nil can report known nonzero counts, but its zero counts remain unknown to observations; supply explicit presence when measured zeros matter. Existing `RunResult.Usage` sums returned values independently of observation presence. Callbacks run synchronously, in order for one operation, and can overlap for different operations. Synchronize shared callback state and avoid blocking on work that needs the same run's admission slot. Callback panics propagate; there is no durable event history or promise of a terminal event after panic. Events omit content and error objects, but selected identifiers may still be sensitive. See the [observation GoDoc](../../observations.go) for exact fields. The [offline proposal and assessment example](../../examples/go-library/observe/main.go) shows application-owned event collection and a separate semantic acceptance decision using the built-in HTTP path without network access. ### Repair A Structured Result Set a small additional-call budget when a structurally invalid result can be corrected automatically: ```go request.Validation = &promptkit.OutputContract{ Format: promptkit.FormatJSON, ValidationMode: promptkit.ValidationJSONSchema, SchemaPath: "events.schema.json", RepairAttempts: 1, } ``` Each repair attempt is another model generation, so it can increase latency and usage; `RunResult.Usage` is cumulative and `Validation.RepairAttempts` reports repair generations rather than transient HTTP attempts. Exhaustion still returns the final failed validation result. `basic` validation can also repair an empty candidate, but structural validity is not evidence of factual or domain correctness. See the [output-contract format reference](../formats.md#output-contract) and [`OutputContract` GoDoc](../../types.go) for the exact budget and eligibility rules. The built-in client also retries eligible temporary HTTP statuses. By default, each generation can make two additional attempts; a repair generation receives its own budget. Set `Config.TransientRetries` to a pointer to zero for the previous single-attempt behavior, or set `RunRequest.TransientRetries` to override one execution. A custom HTTP transport still uses this policy; injected `LLMClient` implementations retain their own retry policy. Application semantic retries are separate runs and can multiply provider traffic further. The caller context covers capacity and retry waiting as well as attempts; without a deadline a valid provider delay can be long. Promptkit does not infer exactly-once or complete cost accounting from a retry outcome. See the [provider integration contract](../integrations/openai-compatible-chat.md#transient-status-retries) for eligible statuses, waits, timeouts, and provider timing. ### Append Already-Rendered Messages An application can include an earlier assistant response and its own corrective instruction in a fresh request without changing the configured prompt: ```go request.AppendedMessages = []promptkit.RenderedMessage{ {Role: promptkit.RoleAssistant, Content: previousResponse}, {Role: promptkit.RoleUser, Content: correction}, } result, err := engine.Run(ctx, request) ``` These messages are already rendered: Promptkit does not template or resolve files in them, and they can contain sensitive model output or application feedback. Every `Run` call uses the engine's captured configuration; the application owns any semantic retry budget. When a pre-execution equality check is required, use `PrepareExecution` and compare its opaque rendered-prompt hash before invoking `RunPrepared`. ## Inputs, Profiles, And Overrides Use `File`, `Inline`, or `InlineWithURI` to construct request inputs. A request can select a profile explicitly or use the prompt's default profile, and can replace execution settings or the complete output contract. The [public value GoDoc](../../types.go) defines nil, empty, zero, replacement, copy, and credential behavior. The [framework format reference](../formats.md) defines how those request values interact with prompt definitions, layered profiles, built-ins, schemas, and framework defaults. For programmatic profiles, use [`ProfileSource`](../../profiles.go) with keyed `ProfileConfig` values. Nil fields are omitted; pointers preserve explicit zero, false, and blank values. Raw `configkit.Mapping` expresses explicit null. ### Migrate From The Previous Configuration API Replace `Config.PromptDir` and `Config.ProfileDir` with selected ConfigKit inputs in `Config.Sources`. The removed `WithPromptFS`, `WithPromptFile`, `WithProfileFS`, `WithProfileFile`, `WithFallbackProfileFS`, `WithProfiles`, and `WithBackend` options have no compatibility wrappers. Use `DirectoryInputs` or `FilesystemInputs` for a strict-composition file group, ConfigKit file inputs for individual files, and `ProfileSource`, `BackendSource`, or `configkit.Mapping` for programmatic values. Backend IDs are keys in configuration, not registrations. Choose caller underlays in fallback-to-operator-to-programmatic order; use `Sources.Main` for one final highest-priority input. Move embedded `id` and `version` fields into keyed `profiles` and `prompts` maps; convert `base_profile` to ConfigKit `extends`. IDs and references are exact literal keys, without trimming. A supplied zero, false, or empty string is now distinguishable from omission; null can clear or suppress an inherited field according to the [field table](../formats.md#defaults-and-overrides). Maps merge recursively across inheritance and layers. `{}` does not clear an inherited map: use null to clear the whole map. At request time, a literal `extra_params["extends"]` is ordinary provider data, not an inheritance instruction; a nonempty request ExtraParams map replaces configured params. Changing a model or backend does not clear unrelated explicit fields, though backend-derived defaults are applied after inheritance to the final backend. Invalid final definitions, including unused ones, now fail `NewEngine`; only request-dependent failures remain at preparation or execution. Prompt text and static templates are captured at construction, not automatically reloaded between runs. Replace the engine to pick up changed definitions or message files. The [format reference](../formats.md) owns exact field and presence rules; the maintained examples show complete runnable workflows. ### Choose A Profile Ownership Model Choose the profile source that matches the application's continuity needs: - Select `promptkit-openrouter-standard` or `promptkit-openrouter-auto` when the maintained literal name is the desired consumer contract. Their current selections are in the [format reference](../formats.md#built-in-profile-catalog). - Select an external catalog profile directly when the application accepts that profile's catalog volatility. The selected external inventories are linked from the [format reference](../formats.md#external-profile-inventories). - Define a complete application-owned profile with an explicit backend or endpoint, model, and settings when the application owns those choices. An application profile that inherits an external base follows later changes to that base and fails if the base disappears; it is not a frozen copy. Promptkit tests the catalog versions selected in its module graph. Independently advancing a catalog dependency can remove an inherited base and produce an untested combination. Pinning the resolved module graph preserves the embedded data for a build, but cannot preserve provider availability. Running binaries neither download catalog updates nor automatically replace withdrawn models. See the [release policy](../policy/release.md#catalog-profile-compatibility) for the exact cross-version guarantees. ### Alias A Built-In Profile Give an application-owned profile ID a built-in base when prompts should select the application ID while inheriting the built-in target. The child can override only the setting it owns: ```go profile := promptkit.ProfileSource("application profiles", map[string]promptkit.ProfileConfig{ "weather-light": { Extends: &standardID, ReasoningEffort: &high, }, }) ``` Place `profile` in a ConfigKit underlay, with `standardID` set to `"promptkit-openrouter-standard"` and `high` to `"high"`. Select `weather-light` in a prompt or `RunRequest.ProfileID`; it remains the reported selected profile. Definition IDs and inheritance references are exact keys. If the base is an external profile, apply the volatility guidance above when deciding whether to use an application-owned complete definition instead. ### Use The Rakestrawhome Built-In Profile Set `RAKESTRAWHOME_INFERENCE_API_KEY` in the application environment, then select `rakestrawhome-gemma-4-31b` as an ordinary profile ID. For example, a prepared result identifies the selected built-in through `BackendRakestrawHome`: ```go prepared, err := engine.Prepare(ctx, promptkit.RunRequest{ PromptID: "meeting.summary", ProfileID: "rakestrawhome-gemma-4-31b", Inputs: inputs, }) if err != nil { return err } if prepared.SelectedBackendID != promptkit.BackendRakestrawHome { return fmt.Errorf("unexpected backend %q", prepared.SelectedBackendID) } ``` The maintained catalog supplies this backend, and callers may overlay its effective configuration through a later ConfigKit source. Direct request credentials and runtime endpoint overrides remain supported under their ordinary GoDoc and format contracts. ### Inspect A Profile Before Prompt Work Use [`Engine.InspectProfile`](../../engine.go) to validate one configured profile without constructing a synthetic prompt or placeholder inputs. It resolves the profile's effective target but does not prepare or execute a prompt: ```go inspection, err := engine.InspectProfile(ctx, profileID) if err != nil { return err } target := inspection.EffectiveModelParams if target.APIKeyEnv != "" { // This is a configured optional environment lookup source. } else if inspection.APIKeyRequired { // Arrange a direct credential before later execution. } ``` Use this configuration-time boundary when only the profile and its target need checking. Use `Prepare` when the application also needs prompt, input, schema, or rendering work; use prepared execution when that work must remain tied to a later execution. A reported `APIKeyEnv` is a configured optional source, while `APIKeyRequired` is the explicit local requirement. The [credential format reference](../formats.md#credentials) and the method's [GoDoc](../../engine.go) own the exact precedence, timing, result, and error contracts. ### Set A Per-Run Session And Reasoning Supply a direct session ID when one prompt should be correlated with a consumer-managed conversation or workflow without changing prompt variables: ```go reasoning := "high" result, err := engine.Run(ctx, promptkit.RunRequest{ PromptID: "meeting.summary", SessionID: "conversation-42", Inputs: map[string]promptkit.ArtifactRef{ "note": promptkit.Inline("Synthetic meeting notes"), }, Execution: &promptkit.ExecutionTargetOverride{ ReasoningEffort: &reasoning, }, }) ``` A nil reasoning pointer inherits the selected profile, a pointer to a nonblank string replaces it, and a pointer to a blank string disables reasoning for that run. Session IDs are correlation metadata, not credentials; use stable, non-secret values that are safe to expose to collaborators and providers. The [`RunRequest` and `ExecutionTargetOverride` GoDoc](../../types.go) owns the exact normalization, precedence, error, copying, and exposure contract. ### Configure A Local OpenAI-Compatible Endpoint Choose the smallest configuration that fits how the endpoint will be reused. #### Use An Endpoint-Only Profile Put the endpoint directly on a programmatic profile when only that profile needs it and shared backend identity or capacity policy is unnecessary: ```go endpoint, model := "http://localhost:8000/v1", "example-model" profile := promptkit.ProfileSource("local profile", map[string]promptkit.ProfileConfig{ "local-summary": {Endpoint: &endpoint, Model: &model}, }) // Add configkit.Input{Source: profile} to a caller underlay. ``` Endpoint-only profiles have an empty backend ID and remain unrestricted by backend capacity policy. #### Use The Conventional Local Backend Use `LocalBackend` when profiles should share the conventional `local` identity, endpoint, and concurrency limit: ```go backendID, model := promptkit.BackendLocal, "example-model" backend := promptkit.BackendSource("local backend", map[string]promptkit.BackendConfig{ backendID: promptkit.LocalBackend("http://localhost:8000/v1", 2), }) profile := promptkit.ProfileSource("local profile", map[string]promptkit.ProfileConfig{ "local-summary": {Backend: &backendID, Model: &model}, }) // Add both sources as inputs in a caller underlay. ``` The helper does not choose an ID or read environment variables. A positive concurrency limit with omitted queue capacity selects the default waiting capacity of 1024. #### Configure A Complete Backend Use a keyed `BackendConfig` value for authentication, extra request parameters, an explicit queue capacity, a custom ID, or multiple local endpoints: ```go endpoint, keyName, limit, noWaiting := "http://gpu-host:8000/v1", "LOCAL_GPU_API_KEY", 2, 0 backend := promptkit.BackendSource("GPU backend", map[string]promptkit.BackendConfig{ "local-gpu": { Endpoint: &endpoint, APIKeyEnv: &keyName, ConcurrencyLimit: &limit, QueueCapacity: &noWaiting, ExtraParams: map[string]any{"provider_option": "enabled"}, }, }) ``` Place `backend` in a caller underlay and select `"local-gpu"` from a `ProfileConfig.Backend` pointer or a keyed file profile. All backend contributions use the same ConfigKit layering rules, including overlays of maintained definitions. The [`BackendConfig` and `LocalBackend` GoDoc](../../backends.go) defines exact omission and construction behavior. Both file-backed and programmatic profiles select a backend through `backend` or `ProfileConfig.Backend`. Profile and request endpoint overrides retain that routing and capacity identity. `PreparedRun.SelectedBackendID`, `RunResult.SelectedBackendID`, and the effective `ExecutionTarget.BackendID` expose it to consumers and injected model clients. Endpoint-only profiles remain supported and expose an empty backend ID. ### Limit Backend Concurrency Set `BackendConfig.ConcurrencyLimit` when a backend needs protection from too many simultaneous model calls. Leaving `QueueCapacity` nil, as in the local-backend example above, selects the default waiting capacity of 1024. To accept no waiting backlog beyond the active calls, provide an explicit zero: ```go noWaiting := 0 backend := promptkit.BackendConfig{ Endpoint: &endpoint, ConcurrencyLimit: &limit, QueueCapacity: &noWaiting, } ``` The pointer distinguishes an explicit zero from omission. Use keyed `BackendConfig` literals inside `BackendSource` for programmatic definitions. Capacity belongs to one engine and the selected backend ID; endpoint-only profiles and custom backends without a configured limit remain unrestricted. Exact validation, defaulting, ownership, and concurrency semantics belong to the [`BackendConfig` GoDoc](../../backends.go). ## Credentials File-backed and programmatic profiles can name an environment variable or require a direct request key. Direct keys are request-scoped and are excluded from supported JSON values and the package's `String` and `GoString` summaries. The exact precedence and redaction guarantees belong to [`RunRequest`, `GenerateRequest`, and the profile GoDoc](../../types.go). ## Protect Files And Generated Data The default artifact reader opens a `File` reference as a caller-selected operating-system path. It does not constrain paths to an application root, impose an inbound request-size policy, or establish an untrusted-input security boundary. Applications must validate and restrict untrusted paths and payloads before constructing a request, or inject an artifact reader that enforces their filesystem, authorization, and size policies. Rendered messages, input and output artifact bodies, raw model output, and validation diagnostics can contain sensitive data. API-key redaction does not sanitize those values. Treat prepared values, results, collaborator requests, errors, and logs according to the application's data-access, retention, and secret-handling policies. ## Extension Interfaces Inject an [`LLMClient` or `ArtifactReader`](../../types.go) when the built-in behavior does not fit the application. Their GoDoc defines concurrent use, context handling, ownership of copied values, nil responses, and preservation of collaborator errors. Implementations must honor cancellation, safely manage copies they retain, avoid unsafe logging of content or credentials, and enforce the application policy that motivated the injection. ## Handle Errors Use `errors.Is` with the [public error sentinels and operation GoDoc](../../engine.go). The declarations distinguish invalid construction, invalid requests, absent selections, collaborator failures, and operational validation failures. Specific request conditions may also match the broader `ErrInvalidRequest`, and injected collaborator identities are preserved where documented. Invalid effective definitions, including unknown backend references, match `ErrInvalidConfig` during construction. ConfigKit load errors remain available through `errors.As`. When a limited backend has admitted all active and waiting calls, handle `ErrCapacityExceeded` separately from request errors and provider failures: ```go result, err := engine.Run(ctx, request) if errors.Is(err, promptkit.ErrCapacityExceeded) { var capacityErr *promptkit.CapacityError if errors.As(err, &capacityErr) { // Record capacityErr.BackendID using application-owned diagnostics. } // Apply application policy: shed work, report overload, or retry later. } ``` A rejected call returns no partial result and does not invoke the model client. Promptkit does not prescribe retries or map this error to an HTTP status; those choices remain with the consuming application. The [`CapacityError` GoDoc](../../capacity_error.go) owns the exact typed-error contract, while the [`Engine.Run` and error GoDoc](../../engine.go) owns broad error and cancellation identities. For a non-2xx response from the built-in OpenAI-compatible client, inspect the status and deliberately selected provider diagnostic when useful: ```go var generationErr *promptkit.GenerationError if errors.As(err, &generationErr) { status := generationErr.StatusCode() message := generationErr.ProviderMessage() _, _ = status, message // Apply application presentation policy. } ``` All provider fields are untrusted and can contain sensitive request or schema fragments. Do not log, display, or return them without an application-specific disclosure policy. Promptkit does not assign presentation behavior. The [`GenerationError` GoDoc](../../generation_error.go) owns the exact typed error contract. Generation failures also match `ErrTransport` when the built-in HTTP request fails, or `ErrMalformedResponse` when a successful provider response cannot be decoded or an injected client returns a nil successful response. Both retain `ErrLLMGenerate`; transport failures retain their original causes for `errors.Is` and `errors.As`. Caller cancellation and deadlines retain their standard context identities. Arbitrary injected-client errors retain their own identities without Promptkit guessing a failure category from their text. An eligible built-in status that consumes a positive retry budget additionally matches `ErrRetriesExhausted`; use `errors.As` to read `RetryExhaustedError.Attempts()` and the final `GenerationError` status. With explicit zero retries, the first eligible status is an ordinary generation error, not exhaustion. Completed structural rejection remains a normal result with failed validation, while an operational error returns no partial result. ## Application Boundary Promptkit is an importable library. It does not own a command, inbound HTTP API, process configuration, or deployment policy. Applications map the root package's results and errors into those concerns, including inbound size and trust policy.