Document keyed configuration and migrated consumer workflows

This commit is contained in:
2026-09-30 03:34:15 +00:00
parent aba5e960aa
commit f17a38f60b
5 changed files with 312 additions and 326 deletions

View File

@@ -8,7 +8,8 @@ gitea.maximumdirect.net/eric/promptkit
``` ```
The root `promptkit` package provides the supported public engine. Consumers The root `promptkit` package provides the supported public engine. Consumers
can configure filesystem or in-memory prompt, profile, and schema sources, can configure file, embedded, or programmatic backend, profile, and prompt
definitions alongside filesystem or in-memory schemas,
prepare requests without generation, run requests with the built-in prepare requests without generation, run requests with the built-in
OpenAI-compatible client, or inject their own model client and artifact reader. OpenAI-compatible client, or inject their own model client and artifact reader.
@@ -20,9 +21,12 @@ Run the maintained offline preparation example from the repository root:
go run ./examples/go-library/prepare go run ./examples/go-library/prepare
``` ```
It loads a repository-local prompt, supplies an in-memory profile and inline It loads a repository-local prompt, supplies a programmatic profile and inline
input, and prints deterministic preparation metadata without contacting a input, inspects the captured configuration, and prints deterministic
provider or requiring credentials. Read the preparation metadata without contacting a provider or requiring credentials.
The [consumer guide](docs/consumers/pkg-promptkit.md) also shows a local backend,
ordered file/embedded sources, programmatic mappings, and how to replace an
engine when source files change. Read the
[example source](examples/go-library/prepare/main.go), the [example source](examples/go-library/prepare/main.go), the
[Go package consumer guide](docs/consumers/pkg-promptkit.md), and the [Go package consumer guide](docs/consumers/pkg-promptkit.md), and the
[framework format reference](docs/formats.md) to build a consumer workflow. [framework format reference](docs/formats.md) to build a consumer workflow.

View File

@@ -78,6 +78,32 @@ explicitly selected missing or unreadable root is an error. Each helper finds
caller may enable imports on individual inputs. A nil `Sources.Main` is an caller may enable imports on individual inputs. A nil `Sources.Main` is an
empty main source, not another directory lookup. 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.
## Inspect The Captured Configuration ## Inspect The Captured Configuration
Use [`Engine.InspectConfiguration`](../../configuration_inspection.go) for an Use [`Engine.InspectConfiguration`](../../configuration_inspection.go) for an
@@ -323,6 +349,38 @@ For programmatic profiles, use [`ProfileSource`](../../profiles.go) with keyed
`ProfileConfig` values. Nil fields are omitted; pointers preserve explicit `ProfileConfig` values. Nil fields are omitted; pointers preserve explicit
zero, false, and blank values. Raw `configkit.Mapping` expresses explicit null. 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 A Profile Ownership Model
Choose the profile source that matches the application's continuity needs: Choose the profile source that matches the application's continuity needs:

View File

@@ -2,394 +2,309 @@
## Purpose ## Purpose
This document is the canonical contract for Promptkit prompt-definition, This is the canonical reference for Promptkit's backend, profile, prompt, and
profile, and schema files. The [Go package consumer guide](consumers/pkg-promptkit.md) schema definitions. The [Go consumer guide](consumers/pkg-promptkit.md) shows
explains how to select these sources and invoke the engine. The how to select sources and use an engine; the
[OpenAI-compatible integration contract](integrations/openai-compatible-chat.md) [OpenAI-compatible integration contract](integrations/openai-compatible-chat.md)
owns the resulting outbound wire behavior. owns outbound wire behavior. [ConfigKit](https://gitea.maximumdirect.net/eric/ConfigKit)
owns the generic YAML grammar, imports, layering, inheritance, and diagnostics.
This reference describes what Promptkit does with the final effective values.
Prompt and profile sources recursively discover files ending in `.yaml` or A configuration document has keyed top-level `backends`, `profiles`, and
`.yml`. Each prompt-definition and profile file contains exactly one YAML `prompts` mappings. Their keys are literal IDs, not filename-derived identities.
document; comments and trailing whitespace are allowed. YAML decoding is Prompt versions are a second keyed mapping. Keys and references must be
strict: unknown fields are errors for the selected definition. Definitions are nonblank and remain exact: Promptkit does not trim or re-key them. Old embedded
selected by their YAML `id`, not their file name or directory. `id`, `version`, and `base_profile` fields are invalid. Unknown fields are
errors.
```yaml
backends:
local:
endpoint: http://localhost:8000/v1
concurrency_limit: 2
profiles:
local-summary:
backend: local
model: example-model
prompts:
meeting.summary:
"1.0.0":
default_profile: local-summary
messages:
- role: user
content: Summarize this note.
output:
format: text
validation_mode: none
```
Every effective backend, profile, and prompt definition is validated when
`NewEngine` constructs the engine, even if it is never selected. A profile may
be partial: an endpoint and model need only be complete after request overrides
are applied. A prompt's default profile similarly need not be runnable without
an overriding request. Missing request data, artifacts, schemas, credentials,
and provider work remain runtime concerns.
The engine captures effective definitions, referenced message-file contents,
and static template syntax at construction. It does not reread configuration or
message files on ordinary runs; construct a new engine to observe edits.
Schema and request artifact reads still happen during preparation. Use
`InspectConfiguration` for an explicit effective-configuration or provenance
report, `InspectProfile` for backend-completed profile settings, and preparation
for request-specific rendered details.
## Prompt Definitions ## Prompt Definitions
A prompt definition describes inputs, Go-template messages, an optional The `prompts` mapping selects a literal prompt ID, then a string version key:
default profile, and an output contract.
```yaml ```yaml
id: meeting.summary prompts:
version: "1.0.0" meeting.summary:
default_profile: local-summary "1.0.0":
description: Summarize a synthetic meeting note. default_profile: local-summary
session_id: '{{.session}}' description: Summarize a synthetic meeting note.
inputs: session_id: '{{.session}}'
- name: note inputs:
required: true - name: note
content_type: text/plain required: true
description: Meeting note to summarize. content_type: text/plain
messages: description: Meeting note to summarize.
- role: system messages:
content: Return a concise summary. - role: system
cache_control: content: Return a concise summary.
type: ephemeral cache_control:
ttl: 1h type: ephemeral
- role: user ttl: 1h
content_file: ./summary.user.md - role: user
output: content_file: ./summary.user.md
format: markdown output:
validation_mode: basic format: markdown
repair_attempts: 0 validation_mode: basic
repair_attempts: 0
``` ```
| Field | Required | Meaning | `messages` must be nonempty and `output` must be a valid mapping. Optional
| --- | --- | --- | `default_profile`, `description`, `session_id`, `inputs`, and cache-control
| `id` | yes | Non-empty prompt identifier used by `RunRequest.PromptID`. | fields may be absent or cleared with null where appropriate. A null whole
| `version` | yes | Non-empty version selected by an optional `RunRequest.PromptVersion`. | definition, required role, output format, or validation mode is invalid.
| `default_profile` | no | Non-empty profile ID used when the request omits `ProfileID`. | `required` and `repair_attempts` default to false and zero, respectively, and
| `description` | no | Human-readable description. | are not nullable. A request that omits a prompt version succeeds only if that
| `session_id` | no | Go template rendered from request variables and input helpers. | prompt ID has exactly one effective version. `InspectPrompt` selects from the
| `inputs` | no | Declared input metadata. | same captured definitions without rendering or resolving a profile.
| `messages` | yes | One or more chat-message templates. |
| `output` | yes | Output format and validation settings. |
When a request omits a version, the selected prompt ID must identify exactly
one definition. When it supplies a version, the ID and version pair must be
unique.
Exact prompt inspection uses this same configured source, strict decoding,
referenced content-file resolution, and ID/version selection. It reports the
selected definition's declared metadata without changing the prompt format or
executing the definition.
### Inputs ### Inputs
Each `inputs` item has these fields: Each `inputs` item may contain `name`, `required`, `content_type`, and
`description`. `name` is required, nonblank, and unique within the prompt.
| Field | Required | Meaning | Omitted `required` means false. Null `inputs` means no declarations. Requests
| --- | --- | --- | supply inline or file-backed `ArtifactRef` values; declared required inputs
| `name` | yes | Non-empty name used by the request input map and `input` template helper. Names must be unique. | must be present. A template reference also needs its named input, even when
| `required` | no | When true, preparation fails if the request omits the input. The default is false. | undeclared. Extra request inputs are allowed.
| `content_type` | no | Expected media-type metadata. |
| `description` | no | Human-readable input description. |
Requests supply inputs as inline or file-backed `ArtifactRef` values. Declared
required inputs must be present. A template reference also requires the named
input to exist, whether or not it was declared. Extra request inputs are
allowed.
### Messages And Templates ### Messages And Templates
Each message has a `role` that Promptkit trims and lowercases. It must then be Each message needs a role and exactly one nonnull `content` or `content_file`.
exactly one of `developer`, `system`, `user`, or `assistant`; blank, custom, An explicitly empty inline `content` is valid. Null clears either content
`tool`, and `function` roles are invalid. This intentionally tightens the selection. Roles are trimmed and lowercased, then must be `developer`,
previous nonblank-string rule. Consumers migrating to the next minor release `system`, `user`, or `assistant`. Static template syntax is checked during
must update any nonstandard prompt-definition roles before upgrading. construction; missing variables and named inputs fail when rendered.
Request variables are template data (`{{.audience}}`), and
`{{input "note"}}` renders an input body.
Each message also has exactly one of: `content_file` uses the effective field's own source context, including when
inherited or imported. Its relative path is resolved against that source's
base directory within its rooted filesystem. Absolute paths and lexical
escapes from the selected root fail. Host-file sources also prevent symlink
escapes. Promptkit preserves the path text, including meaningful whitespace,
after checking that it is not blank. A source without a unique usable resource
context must supply one explicitly or override the field. Content is captured
at construction, not reopened for each request.
- `content`, containing an inline Go template; or Optional `session_id` uses the same template variables and input helper. Its
- `content_file`, naming a file whose contents are the Go template. rendered value is trimmed, omitted if empty, and limited to 256 Unicode code
points. A nonblank direct request session ID bypasses the template.
`content_file` must be a relative path. It resolves from the directory that
contains the prompt file and must remain within the configured prompt source
root; parent components are allowed only when the resolved target remains
inside that root. Absolute paths and paths that escape the root are rejected.
Operating-system directory and single-file sources also reject symlink targets
outside the root, while injected `fs.FS` sources apply containment in that
filesystem's relative path namespace. For `WithPromptFile`, the source root is
the directory containing the selected prompt file. Promptkit uses the parsed
path text exactly after checking separately that it is not blank, so leading
and trailing whitespace can name real filesystem entries.
Request variables are the template data, so a variable named `audience` is
referenced as `{{.audience}}`. The `{{input "note"}}` helper renders the body
of a named input. Missing variables and input references are errors.
The optional `session_id` uses the same template data and input helper. Its
rendered value is trimmed, omitted when empty, and limited to 256 Unicode code
points. A nonblank direct request session ID bypasses this template completely;
a blank direct value leaves the template behavior unchanged.
### Cache Control ### Cache Control
`cache_control` is optional and has these fields: Optional `cache_control` has required `type: ephemeral` and optional `ttl`,
which may be empty or `1h`. Promptkit retains this metadata on rendered
| Field | Required | Values | messages; the integration contract defines its wire representation.
| --- | --- | --- |
| `type` | yes | `ephemeral` |
| `ttl` | no | Empty or `1h` |
Promptkit preserves cache-control metadata on the rendered message. The
outbound integration determines its wire representation.
### Output Contract ### Output Contract
| Field | Required | Values or behavior | | Field | Required | Values or behavior |
| --- | --- | --- | | --- | --- | --- |
| `format` | yes | `text`, `markdown`, or `json`. | | `format` | yes | `text`, `markdown`, or `json` |
| `validation_mode` | yes | `none`, `basic`, `json`, or `json_schema`. | | `validation_mode` | yes | `none`, `basic`, `json`, or `json_schema` |
| `schema_path` | for `json_schema` | Path to a schema in the configured schema source. | | `schema_path` | for `json_schema` | Path in the selected schema source |
| `repair_attempts` | no | Integer from zero through three; omitted means zero. A positive value requires `basic`, `json`, or `json_schema` validation. | | `repair_attempts` | no | Integer 0–3; default 0. Positive values need `basic`, `json`, or `json_schema` |
The validation modes behave as follows: `none` skips content validation; `basic` requires nonempty content; `json`
accepts any valid JSON value; `json_schema` checks the selected schema.
- `none` skips content validation; `format` determines output artifact metadata. JSON Schema mode also supplies
- `basic` requires non-empty generated content; structured-output metadata to compatible clients. Plain `json` does not
- `json` requires valid JSON; and request a provider-native JSON-object constraint. A positive repair budget
- `json_schema` requires valid JSON that satisfies the selected schema. counts additional generations after failed validation; exhaustion returns the
last candidate and its failed validation result. Operational errors remain
`format` controls output artifact metadata. JSON Schema mode also supplies the errors. A request-level `OutputContract` replaces the whole configured one,
schema to compatible model clients as structured-output metadata. Plain `json` not individual fields; an empty request format defaults to `text`.
validation accepts every valid JSON value and does not request a provider-native
JSON-object constraint.
`repair_attempts` counts additional generation calls after a failed validation.
Zero is single-pass. With a positive eligible budget, Promptkit stops at the
first valid candidate. If the budget is exhausted, it returns the final
candidate and its complete failed validation result; generation and operational
validation failures remain errors. `none` never permits repair.
A request-level `OutputContract` replaces the complete prompt output contract.
It does not merge individual fields. If its format is empty, Promptkit uses
`text`.
## Built-In Backends ## Built-In Backends
Every engine provides these reserved OpenAI-compatible backend IDs. Consumers The selected catalog modules contribute these backend definitions as the
must not register either ID with `WithBackend`; exact registration and earliest underlay. Their settings can be overlaid by caller sources; these IDs
reservation behavior belongs to the [`Backend` GoDoc](../backends.go). are not reserved against configuration changes.
| ID | Base endpoint | API-key environment variable | Active generation limit | Default queue capacity | | ID | Catalog endpoint | API-key environment name | Active generation limit |
| --- | --- | --- | ---: | ---: | | --- | --- | --- | ---: |
| `openrouter` | `https://openrouter.ai/api/v1` | `OPENROUTER_API_KEY` | 16 | 1024 | | `openrouter` | `https://openrouter.ai/api/v1` | `OPENROUTER_API_KEY` | 16 |
| `rakestrawhome` | `https://inference.ai.rakestrawhome.com/v1` | `RAKESTRAWHOME_INFERENCE_API_KEY` | 4 | 1024 | | `rakestrawhome` | `https://inference.ai.rakestrawhome.com/v1` | `RAKESTRAWHOME_INFERENCE_API_KEY` | 4 |
A backend definition may have `endpoint`, `api_key_env`,
`concurrency_limit`, `queue_capacity`, and `extra_params`. The final endpoint
is required and must be an absolute HTTP(S) URL with a host, no user
information, query, or fragment. An omitted or null API-key environment name
means no lookup. Omitted/null concurrency is zero (unlimited); a supplied
negative value is invalid. For positive concurrency, omitted/null queue
capacity defaults to 1024. An explicit queue capacity, including zero,
requires positive concurrency and must be nonnegative. Unlimited concurrency
has no queue. Provider maps contain JSON-compatible values; null entries are
ordinary JSON null. Unknown fields, including a raw `api_key`, fail.
## Profile Definitions ## Profile Definitions
A profile supplies model execution settings: Profiles are keyed by literal ID and may inherit a named parent with
`extends`:
```yaml ```yaml
id: local-summary profiles:
backend: openrouter local-summary:
model: example-model backend: local
temperature: 0.2 model: example-model
max_tokens: 500 temperature: 0.2
top_p: 0.95 max_tokens: 500
timeout_seconds: 90 timeout_seconds: 90
service_tier: flex extra_params:
reasoning_effort: medium provider_option: enabled
extra_params: local-summary-fast:
provider_option: enabled extends: local-summary
timeout_seconds: 30
reasoning_effort: low
``` ```
A derived profile can use a named base and override only the settings it owns: Profiles may also supply `endpoint`, `top_p`, `service_tier`, `api_key_env`,
and `api_key_required`. Promptkit does not infer a backend from an endpoint or
```yaml model. A profile with only an endpoint has no backend capacity identity.
id: local-summary-fast Nonblank endpoints follow the backend URL rules; temperature is 0–2, top_p
base_profile: local-summary is 0–1, max_tokens and timeout_seconds are nonnegative. Provider maps accept
timeout_seconds: 30 JSON-compatible values with nonempty keys and cannot collide with the built-in
reasoning_effort: low client's [standard request fields](integrations/openai-compatible-chat.md#request-body).
``` Credential values do not belong in profiles: raw `api_key` is invalid.
| Field | Required | Meaning |
| --- | --- | --- |
| `id` | yes | Profile identifier, trimmed before selection and publication. It must be non-empty after trimming and unique within one source after normalization. |
| `base_profile` | no | One optional parent profile ID. A derived profile may inherit target fields from it. |
| `backend` | unless `endpoint` is present | Backend registry ID. It is trimmed and registry membership is checked when the profile is prepared or inspected. |
| `endpoint` | unless `backend` is present | OpenAI-compatible base URL, including an API version path when required. A nonempty value is trimmed and must be absolute HTTP or HTTPS with a host and without user information, a query, or a fragment. When both connection fields are present, this overrides the backend endpoint without changing backend identity. |
| `model` | yes | Non-empty provider model name. |
| `temperature` | no | Number from 0 through 2. |
| `max_tokens` | no | Integer zero or greater. |
| `top_p` | no | Number from 0 through 1. |
| `timeout_seconds` | no | Per-generation deadline in whole seconds; integer zero or greater. |
| `service_tier` | no | Provider-specific request tier. |
| `reasoning_effort` | no | Provider-specific reasoning setting. |
| `api_key_env` | no | Optional environment-variable lookup source for an API key. |
| `extra_params` | no | JSON-compatible provider-specific outbound fields. |
Raw `api_key` is prohibited in profile YAML. Store only an environment
variable name in `api_key_env`.
A standalone profile must provide a model and at least one of `backend` or
`endpoint`. A derived profile may omit those target fields because its selected
base chain can provide them. Local parsing still validates a derived profile's
own ID, supplied endpoint, execution-setting bounds, and `extra_params`.
Promptkit does not infer a backend from a model or endpoint. Endpoint-only
profiles remain supported and have no effective backend ID.
The engine always provides the built-in `openrouter` and `rakestrawhome` IDs.
Consumers can add engine-scoped IDs with
[`WithBackend`](../backends.go); exact registration validation belongs to its
GoDoc.
`extra_params` accepts null, booleans, finite numbers, strings, arrays, and
objects with string keys. Keys must be non-empty. With the built-in client,
they also cannot collide with the standard fields listed in the
[outbound request contract](integrations/openai-compatible-chat.md#request-body).
Excessively deep or large JSON-shaped values are rejected for safety.
### Defaults And Overrides ### Defaults And Overrides
Execution settings resolve in this order: After effective profile inheritance, Promptkit applies the selected backend's
defaults once, then request `ExecutionTargetOverride` values. Backend-derived
settings are not inheritance input: changing a child profile's backend does
not carry the parent's derived endpoint or credential source into it. Changing
only a model or backend does not clear unrelated explicitly inherited fields.
1. the framework timeout baseline; | Profile field | Absent | Explicit value | Explicit null |
2. the selected backend, when the profile names one; | --- | --- | --- | --- |
3. the selected profile; and | `backend` | No backend | Exact backend ID; blank means none | No backend |
4. request `ExecutionTargetOverride` values. | `endpoint` | Selected backend endpoint | Valid nonblank URL; empty clears | Clear, without backend fallback |
| `model` | Unspecified | Nonblank model; empty clears | Unspecified |
| `temperature`, `top_p`, `max_tokens` | Unspecified | Valid value, including outbound zero | Suppress outbound control |
| `service_tier`, `reasoning_effort` | Unspecified | Normalized value; blank suppresses | Suppress outbound control |
| `timeout_seconds` | 600 seconds | Nonnegative; zero disables generation deadline | Reset to 600 seconds |
| `api_key_env` | Backend lookup name, if any | Trimmed name; blank disables | Disable backend lookup |
| `api_key_required` | False | Boolean, including false | False |
| `extra_params` | Backend defaults | Recursively overlay profile map on backend map | Suppress configured provider map |
The framework baseline is: An empty mapping does not clear inherited map entries; null clears the whole
map. Nested maps merge recursively, while scalar and array values replace.
| Setting | Default | Runtime nonempty `ExtraParams` overrides replace the entire configured map.
| --- | --- | Request overrides otherwise follow the [public value GoDoc](../types.go).
| `temperature` | Unspecified and omitted from compatible provider requests unless a profile or runtime override selects it. | For example, a runtime `extra_params["extends"]` key is ordinary provider
| `max_tokens` | Unspecified and omitted from compatible provider requests unless a profile or runtime override selects it. | data, not a configuration inheritance instruction. An explicit zero timeout
| `top_p` | Unspecified and omitted from compatible provider requests unless a profile or runtime override selects it. | does not disable the caller context or HTTP transport timeout.
| `timeout_seconds` | `600` |
Numeric zero in a file or in-memory profile does not select a numeric value.
For `temperature`, `max_tokens`, and `top_p`, it leaves the provider control
unspecified. For `timeout_seconds`, it retains the framework deadline. Numeric
request overrides use pointers, so an explicit zero is retained and sent to
compatible providers. In particular, an explicit request `timeout_seconds` of
zero disables the per-generation deadline while leaving the caller context and
transport timeout intact.
Non-empty profile strings replace backend defaults, and non-empty request
strings replace both. Request reasoning is the exception: a nil
`ReasoningEffort` pointer inherits the profile, a pointer to a nonblank string
trims and replaces it, and a pointer to a blank string clears it. Backend
identity is retained when either layer overrides the endpoint, so the override
also retains any engine-local capacity policy configured for that backend.
Capacity configuration belongs to the Go
[`Backend` API](../backends.go), not prompt or profile YAML. A non-empty
`extra_params` map at each layer replaces the entire lower-precedence map
rather than merging keys.
The [outbound integration contract](integrations/openai-compatible-chat.md)
defines how the effective settings are serialized.
### Source And Profile Precedence ### Source And Profile Precedence
An explicit request profile ID takes precedence over the prompt's `NewEngine` composes catalog definitions first, Promptkit-owned definitions
`default_profile`. If neither is present, preparation fails. Exact profile next, then caller underlays in their given order, with an optional main input
inspection instead takes one explicit profile ID and does not use a prompt last. Later tiers overlay earlier ones. Inputs within one underlay group are
default. strictly composed: conflicting contributions are errors, not priority
signals. The directory helpers discover `.yaml` and `.yml` files recursively
in sorted order, but that order does not give one file higher precedence.
An explicitly selected missing/unreadable root fails; an empty root contributes
nothing. Imports are disabled by these helpers unless the caller enables them
on an input. Single files can use ConfigKit inputs directly.
Profile sources resolve matching IDs in this order: Backends, profiles, and prompts all follow these same source rules. A later
profile or backend can change individual fields of a lower definition. A
1. in-memory profiles supplied with `WithProfiles`; request `ProfileID` takes precedence over a prompt `default_profile`; if
2. the ordinary configured source selected by a profile file, `fs.FS`, or neither is selected, preparation fails. Exact profile inspection selects one
configured profile directory; explicit ID and does not consult a prompt default.
3. application fallback profiles supplied with `WithFallbackProfileFS`; and
4. Promptkit-owned profiles; and
5. maintained external catalog profiles.
A profile source supplies a complete definition; definitions and their fields
are not merged across sources. A higher-precedence source falls back only when
the requested profile ID is absent. An invalid matching profile is an error and
does not fall back. In-memory `Profile` values follow the same ranges as YAML
profiles. They use `APIKeyRequired` for request-scoped credentials instead of
`api_key_env`. Preparation and exact profile inspection use this same source
precedence.
When a selected definition names `base_profile`, every profile ID in that
chain is looked up through this same precedence order. A higher-precedence
definition therefore shadows a lower-precedence definition of the same base
ID, including a built-in. References are not source-qualified.
### Profile Inheritance ### Profile Inheritance
Promptkit resolves one linear base chain of at most 32 profiles, including the ConfigKit resolves `extends` on effective profile definitions. One profile
selected profile. It merges settings from the root base to the selected leaf. may inherit another keyed profile; presence, explicit null, and recursively
The leaf's `id` remains the selected profile identity. Nonblank string fields merged maps determine the final fields. References are literal IDs, including
(`backend`, `endpoint`, `model`, `service_tier`, `reasoning_effort`, and IDs containing punctuation. There is no Promptkit-specific depth limit or
`api_key_env`) and nonzero numeric fields replace inherited values. A nonempty second base-chain resolver. Missing references and cycles fail at construction.
`extra_params` map replaces the complete inherited map rather than merging For generic inheritance and layer mechanics, consult
keys, and `APIKeyRequired: true` remains true through the chain. Backend and [ConfigKit](https://gitea.maximumdirect.net/eric/ConfigKit).
endpoint are independent: replacing one does not clear the other.
There is no profile-level clearing syntax. Blank strings, zero numbers, false,
and empty maps remain unspecified and inherit from a base. Use existing
presence-aware request overrides where an execution needs an explicit zero or
empty reasoning setting.
An absent directly selected profile reports the ordinary not-found error. Once
the selected profile exists, a missing base, cycle, overlong chain, or
incomplete resolved target is a profile-load failure. Ordinary operations
resolve chains afresh; prepared execution retains the fully resolved target.
## Built-In Profile Catalog ## Built-In Profile Catalog
Every built-in profile selects one maintained built-in backend and inherits Promptkit contributes two opt-in definitions; neither is selected by default:
that backend's connection and credential metadata. Profile files do not repeat
those values. A configured, application fallback, or in-memory profile with
the same profile ID takes precedence.
Promptkit owns two opt-in profiles. The following definitions are current
selections, not automatic defaults:
| ID | Current definition | | ID | Current definition |
| --- | --- | | --- | --- |
| `promptkit-openrouter-standard` | Inherits `gemini-flash-latest`. | | `promptkit-openrouter-standard` | Extends the selected OpenRouter catalog's `gemini-flash-latest` profile. |
| `promptkit-openrouter-auto` | Selects backend `openrouter` and model `openrouter/auto`. | | `promptkit-openrouter-auto` | Selects backend `openrouter` and model `openrouter/auto`. |
Select either name through an existing request `profile_id` or prompt Select one through request `ProfileID` or prompt `default_profile`.
`default_profile`. Consumers can replace either definition or its inherited Caller layers may overlay these definitions or their catalog bases. The
base through the ordinary profile-source precedence above. The [release policy](policy/release.md#catalog-profile-compatibility) owns their
[release policy](policy/release.md#catalog-profile-compatibility) defines the cross-version compatibility boundary.
cross-version guarantee for these names.
## External Profile Inventories ## External Profile Inventories
The external profile inventory is defined by the selected catalog modules, not The selected catalog modules, not this document, own their profile inventories:
duplicated here. Browse the exact data selected by this Promptkit version:
- [OpenRouter catalog v2.20260926.0 profiles](https://gitea.maximumdirect.net/eric/promptkit-backend-openrouter/src/tag/v2.20260926.0/catalog/profiles) - [OpenRouter catalog v3.20260930.0 profiles](https://gitea.maximumdirect.net/eric/promptkit-backend-openrouter/src/tag/v3.20260930.0/catalog/profiles)
- [Rakestrawhome catalog v2.20260926.0 profiles](https://gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/src/tag/v2.20260926.0/catalog/profiles) - [Rakestrawhome catalog v3.20260930.0 profiles](https://gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/src/tag/v3.20260930.0/catalog/profiles)
External profile entries are volatile across catalog releases. The These are tested local data, not a promise of live provider availability.
[release policy](policy/release.md#catalog-profile-compatibility) owns the precise The [release policy](policy/release.md#catalog-profile-compatibility) owns
compatibility boundary; a selected module version identifies tested local data, the compatibility boundary.
not live provider availability.
## Schemas ## Schemas
Schemas are JSON documents selected by a prompt or request Schemas are JSON documents selected by a prompt or request `schema_path`.
`schema_path`. For a directory or `fs.FS` source, paths resolve within the Directory and `fs.FS` schema paths resolve within the configured root;
configured source root. Referenced nested schemas resolve relative to the referenced nested schemas resolve relative to the owning schema document.
owning schema document. `WithSchemaFile` exposes one schema, addressed by its `WithSchemaFile` exposes one schema under its base name. An unreadable,
base name. invalid, or unresolvable schema causes an operational validation error.
Valid JSON that fails the schema instead yields a failed validation result.
An unreadable, invalid, or unresolvable schema produces an operational
validation error. Generated content that is valid JSON but does not satisfy the
schema produces a failed validation result.
## Credentials ## Credentials
Credential values belong at the request or environment boundary, never in Only environment-variable names belong in backend/profile definitions, never
prompt, profile, schema, or example files: credential values. A request may supply a direct `APIKey` or an `APIKeyEnv`
override. A direct key takes precedence, followed by a request environment
source, an explicit profile source, and an inherited backend source. A true
profile `api_key_required` suppresses the backend-derived environment source,
but an explicit profile or request environment source remains usable. False
restores ordinary optional-credential behavior. Missing or blank optional
environment values leave the built-in client's Authorization header absent;
required availability is checked at preparation and again during prepared
execution. Injected clients own their credential-resolution behavior.
- a backend or file profile can name an optional environment lookup source Credential values are excluded from JSON results and redacted by public
with `APIKeyEnv` or `api_key_env`; string formatters. ConfigKit inspection redacts credential-shaped dynamic
- an in-memory profile may set `APIKeyRequired` as an explicit local provider keys, but does not make arbitrary prompt/provider content safe to
requirement; disclose. Applications own report disclosure and secret handling.
- a request can provide a direct `APIKey` or override the optional `APIKeyEnv`
source; and
- a direct request key takes precedence over environment lookup.
After a direct request key, the credential-source precedence is request
`APIKeyEnv`, profile `api_key_env`, then the backend default. An in-memory
profile with `APIKeyRequired` clears an inherited backend environment name and
requires a direct key unless the request explicitly supplies `APIKeyEnv`.
Named environment sources are optional: when the selected source is absent,
empty, or whitespace-only, the built-in client omits the `Authorization`
header and handles the provider response normally. `APIKeyRequired` is the
only explicit local availability requirement. Promptkit validates required
credential availability during preparation and rechecks it when a prepared
execution runs. Injected clients receive resolved source metadata but define
their own credential-resolution behavior.
Direct keys are excluded from JSON results and redacted by public string
formatters. Environment-variable names may appear in prepared metadata, but
their values do not.

View File

@@ -543,6 +543,8 @@ The runner has no reference to the inspector or ConfigKit metadata types.
## Stage 10 — Migrate Examples And Canonical Format Guidance ## Stage 10 — Migrate Examples And Canonical Format Guidance
**Completed:** 2026-09-30.
Scope: maintained examples and user-facing documentation. Scope: maintained examples and user-facing documentation.
1. Complete the preparation, execution, and observation example migration begun 1. Complete the preparation, execution, and observation example migration begun

View File

@@ -29,6 +29,13 @@ func main() {
if err != nil { if err != nil {
exit(err) exit(err)
} }
view, err := engine.InspectConfiguration()
if err != nil {
exit(err)
}
if _, found := view.Explain(configkit.Keys("profiles", "offline-example", "model")); !found {
exit(fmt.Errorf("configured profile model was not found"))
}
prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{ prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{
PromptID: "example.prepare", PromptID: "example.prepare",