Add profile inheritance definition support
This commit is contained in:
224
docs/roadmap/profile-inheritance.md
Normal file
224
docs/roadmap/profile-inheritance.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# Profile Inheritance Roadmap
|
||||
|
||||
## Purpose
|
||||
|
||||
Allow a Promptkit execution profile to derive from another profile. This gives
|
||||
downstream consumers stable, application-owned profile IDs without requiring
|
||||
them to copy a built-in or shared profile's model and execution settings.
|
||||
|
||||
For example, Weatherreporter should be able to select `weather-light` from its
|
||||
prompt definitions while defining that profile as an alias or refinement of a
|
||||
Promptkit built-in:
|
||||
|
||||
```yaml
|
||||
id: weather-light
|
||||
base_profile: deepseek-4-flash
|
||||
reasoning_effort: high
|
||||
timeout_seconds: 120
|
||||
```
|
||||
|
||||
Changing only `base_profile` to another profile later should redirect every
|
||||
prompt that selects `weather-light`, without requiring changes to deployed
|
||||
prompt definitions. The inherited profile remains ordinary Promptkit
|
||||
configuration rather than application-specific routing logic.
|
||||
|
||||
## Target End State
|
||||
|
||||
- File and `fs.FS` profile definitions may use the optional `base_profile`
|
||||
field to name one parent profile.
|
||||
- In-memory profiles expose the equivalent `Profile.BaseProfileID` field.
|
||||
`OpenAICompatibleProfileConfig` exposes and forwards the same field so its
|
||||
convenience constructor remains feature-complete.
|
||||
- A profile containing only `id` and `base_profile` is a valid semantic alias.
|
||||
Fields needed for an executable target may be inherited instead of repeated.
|
||||
- Base profiles may come from any configured profile source, including the
|
||||
embedded built-in catalog. Resolution uses the engine's complete assembled
|
||||
profile catalog and its existing source precedence.
|
||||
- A child may refine selected inherited settings using the ordinary profile
|
||||
fields. After inheritance resolves, backend defaults and request overrides
|
||||
retain their existing precedence.
|
||||
- `Prepare`, `PrepareExecution`, `Run`, and `InspectProfile` all use the same
|
||||
inheritance behavior. Prepared execution freezes the fully resolved target
|
||||
and does not reopen the profile chain when it later runs.
|
||||
- Public results continue to report the selected child ID, such as
|
||||
`weather-light`, while effective backend, endpoint, model, credentials, and
|
||||
execution settings reflect the resolved chain.
|
||||
|
||||
## Profile Reference Semantics
|
||||
|
||||
Each profile may name at most one direct base. Bases may themselves inherit,
|
||||
so aliases and refinements can form a linear chain. Multiple inheritance and
|
||||
merging an array of profiles are outside this feature.
|
||||
|
||||
Every ID in a chain is resolved through the same composite catalog used for an
|
||||
ordinary profile selection. Existing precedence therefore applies separately
|
||||
to each lookup:
|
||||
|
||||
1. in-memory profiles;
|
||||
2. the ordinary configured profile source;
|
||||
3. the application fallback profile source; and
|
||||
4. the embedded built-in catalog.
|
||||
|
||||
A higher-precedence definition of a base ID intentionally shadows a lower-
|
||||
precedence definition, just as it would if selected directly. References are
|
||||
not source-qualified, and there is no special syntax for bypassing an override
|
||||
to select a lower-precedence or specifically built-in definition. A profile
|
||||
cannot extend a shadowed definition with its own ID; that is a self-cycle.
|
||||
|
||||
Profile chains are resolved afresh for each ordinary preparation, execution,
|
||||
or inspection operation, preserving the current fresh-source behavior. The
|
||||
resolver does not cache a chain across operations. Mutable sources are not
|
||||
promised a transactional snapshot across separate file reads; callers that
|
||||
need a frozen result use prepared execution.
|
||||
|
||||
## Override Semantics
|
||||
|
||||
Inheritance combines profile definitions from the root base to the selected
|
||||
leaf. The leaf's `id` is always retained, and `base_profile` is resolution
|
||||
metadata rather than an execution setting.
|
||||
|
||||
Child fields use the profile conventions already exposed by Promptkit:
|
||||
|
||||
- nonblank `backend`, `endpoint`, `model`, `service_tier`,
|
||||
`reasoning_effort`, and `api_key_env` values replace inherited values;
|
||||
- nonzero `temperature`, `max_tokens`, `top_p`, and `timeout_seconds` values
|
||||
replace inherited values;
|
||||
- omitted, blank, or zero fields inherit, according to the existing profile
|
||||
contract;
|
||||
- a nonempty `extra_params` map replaces the complete inherited map rather
|
||||
than merging individual keys; and
|
||||
- `APIKeyRequired: true` is inherited and remains sticky through the chain.
|
||||
It retains the existing security behavior of clearing inherited profile or
|
||||
backend environment sources and requiring a direct request key or an
|
||||
explicit request `APIKeyEnv` override.
|
||||
|
||||
This feature does not introduce profile-level presence pointers or clearing
|
||||
syntax. A derived profile cannot use a blank string, numeric zero, false, or an
|
||||
empty map to clear an inherited value because those states already mean
|
||||
"unspecified" for profile configuration. Consumers can use the existing
|
||||
presence-aware runtime overrides when they need an explicit zero or an empty
|
||||
reasoning value. A future clearing syntax may be considered independently if
|
||||
real consumer demand emerges.
|
||||
|
||||
Connection fields retain their existing independent meanings. A child endpoint
|
||||
may override an inherited endpoint without changing backend identity, and a
|
||||
child backend replaces an inherited backend ID. Other fields continue to
|
||||
inherit unless the child supplies their ordinary nonblank or nonzero override.
|
||||
|
||||
## Validation And Failure Behavior
|
||||
|
||||
Profile parsing and registration must distinguish local validity from resolved
|
||||
completeness:
|
||||
|
||||
- IDs, base IDs, supplied endpoints, numeric bounds, extra parameters, and
|
||||
other values that already have source-local rules remain validated at their
|
||||
owning input boundary.
|
||||
- A standalone profile with no base continues to require a model and at least
|
||||
one backend or endpoint.
|
||||
- A profile with a base may omit those required target fields, because the
|
||||
resolved chain may provide them.
|
||||
- The completely merged profile must satisfy the same target invariants as a
|
||||
current standalone profile before backend resolution and use.
|
||||
- In-memory profiles without a base retain their current `NewEngine`
|
||||
validation behavior. Reference existence, cycles, and resolved completeness
|
||||
are evaluated when an inherited profile is selected or inspected, so file
|
||||
and in-memory profiles share one resolution contract and fresh lower sources
|
||||
are not frozen at engine construction.
|
||||
|
||||
Resolution must detect direct and indirect cycles and impose a maximum chain
|
||||
length of 32 profiles, including the selected leaf. The diagnostic should name
|
||||
the relevant profile chain without exposing internal package values.
|
||||
|
||||
Failure identity distinguishes the requested profile from its dependencies:
|
||||
|
||||
- an absent directly selected profile retains `ErrProfileNotFound`;
|
||||
- an existing selected profile whose base is absent, malformed, cyclic,
|
||||
excessively deep, or incomplete fails with `ErrProfileLoad`, not
|
||||
`ErrProfileNotFound`; and
|
||||
- backend lookup and resolved-setting failures retain their existing
|
||||
`ErrProfileLoad` behavior.
|
||||
|
||||
A malformed higher-precedence definition remains authoritative and stops
|
||||
fallback. Inheritance must not silently skip a broken base to use another
|
||||
definition or partially resolve a chain.
|
||||
|
||||
## Architectural Ownership
|
||||
|
||||
Inheritance resolution belongs in `internal/profile`, around the fully
|
||||
assembled composite repository. Individual filesystem, `fs.FS`, in-memory,
|
||||
and built-in repositories continue to own discovery, strict decoding, local
|
||||
normalization, defensive copying, and source precedence; they must not resolve
|
||||
bases independently within their own source.
|
||||
|
||||
The root facade assembles the raw composite catalog and the resolving profile
|
||||
boundary. `internal/usecase` continues to request one selected profile and
|
||||
resolve its backend; it should receive an already combined, caller-owned
|
||||
profile rather than implementing recursion or source traversal itself.
|
||||
|
||||
`internal/domain` may carry the normalized base ID and any internal information
|
||||
needed to distinguish locally supplied fields, but those internal values must
|
||||
not leak through public execution targets or provider requests. Final target
|
||||
resolution still follows:
|
||||
|
||||
1. framework defaults;
|
||||
2. the backend selected by the resolved profile;
|
||||
3. the fully resolved profile; and
|
||||
4. request runtime overrides.
|
||||
|
||||
## Public And Format Surface
|
||||
|
||||
The feature adds only the smallest consumer-facing configuration needed for
|
||||
one-parent inheritance:
|
||||
|
||||
- YAML `base_profile` in the profile format;
|
||||
- `Profile.BaseProfileID`; and
|
||||
- `OpenAICompatibleProfileConfig.BaseProfileID`.
|
||||
|
||||
Exact public behavior belongs in the new fields' GoDoc once implemented. The
|
||||
profile format reference owns YAML syntax, merge rules, chain limits, and
|
||||
validation behavior. Consumer guidance should show one concise alias/refinement
|
||||
workflow and link to those canonical contracts rather than duplicating them.
|
||||
|
||||
No new engine method, profile registry mutation API, public resolver, public
|
||||
inheritance graph, or result provenance field is required. Existing profile
|
||||
selection through prompt defaults and `RunRequest.ProfileID` remains unchanged.
|
||||
|
||||
## Quality And Documentation End State
|
||||
|
||||
Testing should follow the repository's lean ownership model:
|
||||
|
||||
- profile parser and registration tests own the new field, local validation,
|
||||
and defensive copying;
|
||||
- profile resolver tests own cross-source lookup, precedence, chain merging,
|
||||
cycle and depth protection, missing bases, and resolved completeness;
|
||||
- use-case tests retain ownership of backend and runtime-override precedence
|
||||
without duplicating the full inheritance matrix; and
|
||||
- one representative external-package workflow should prove that a downstream
|
||||
alias of a built-in profile reports the child ID and inherited effective
|
||||
target through preparation or inspection.
|
||||
|
||||
All tests remain deterministic, offline, parallel-safe, and independent of
|
||||
real provider credentials. Existing standalone-profile, overlay, malformed-
|
||||
selection, built-in, credential, and prepared-execution contracts must remain
|
||||
green.
|
||||
|
||||
The implemented feature's exact GoDoc, `docs/formats.md`,
|
||||
`docs/internal/sources.md`, and focused consumer guidance must agree. The
|
||||
internal component inventory reflects inheritance resolution as a concrete
|
||||
`internal/profile` responsibility. Release documentation remains a separate
|
||||
release-preparation concern.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Multiple inheritance or ordered profile mixins.
|
||||
- Deep or key-by-key merging of `extra_params`.
|
||||
- Source-qualified references or a special built-in namespace.
|
||||
- Extending a shadowed lower-precedence profile with the same ID.
|
||||
- Per-request changes to the base-profile relationship.
|
||||
- Profile-level clearing syntax or presence-aware scalar fields.
|
||||
- Caching resolved chains across operations or watching sources for changes.
|
||||
- Changing backend registry, concurrency, credential, runtime-override,
|
||||
provider-wire, or prompt-selection semantics beyond applying them to the
|
||||
resolved profile.
|
||||
- Consumer-specific configuration discovery, deployment migration, or routing
|
||||
policy.
|
||||
Reference in New Issue
Block a user