Propose collection configuration using current ConfigKit

This commit is contained in:
2026-10-14 01:15:13 +00:00
parent 4bddbbe8f4
commit 9e5ed08635
2 changed files with 376 additions and 0 deletions

View File

@@ -0,0 +1,372 @@
# Proposal 1: Namespaced Collections With Current ConfigKit
Status: proposed PromptKit configuration standard; not implemented or accepted.
This proposal addresses the pending configuration decision in the
[namespace and collection roadmap](namespaces.md). It requires PromptKit changes
but no ConfigKit extension. Proposal 2, which may assume upstream changes, is
outside this document.
## Recommendation
Keep the existing top-level `backends`, `profiles`, and `prompts` mappings, and
use fully qualified resource IDs as their actual YAML keys. Add a `collections`
mapping keyed by namespace for release metadata. The application's main input
owns `version: 1`; reusable collection contributions omit that top-level field.
Compose independently maintained peer collections in one ConfigKit underlay
group. Put intentional application/operator overrides in later groups or the
main input. This uses ConfigKit's distinction between strict composition and
explicit precedence to protect independent contributions while retaining
ordinary configuration customization.
Namespace identity exists in the configuration tree before composition. A
separate `id` field inside an otherwise unqualified prompt definition would be
too late: ConfigKit merges mapping keys before PromptKit interprets definitions.
## Reviewed Baseline And Evidence
Reviewed the local `../ConfigKit` checkout at commit
`6fffde4af8f31f99d3ee295c426eb3234cb0fb77` (v0.7.1), the version selected by
PromptKit. The governing upstream sources are its
[configuration contract](https://gitea.maximumdirect.net/eric/ConfigKit/src/tag/v0.7.1/docs/config.md)
and [composition/overlay decision](https://gitea.maximumdirect.net/eric/ConfigKit/src/tag/v0.7.1/docs/adr/0001-separate-composition-and-overlay.md).
Implementation evidence includes `internal/engine/compose_test.go`,
`internal/engine/load_request_test.go`, `internal/engine/imports_test.go`, and
the public `VersionRule` and `LoadLayered` declarations in that checkout.
The relevant current rules are:
| ConfigKit behavior | Consequence for this proposal |
| --- | --- |
| Within a composition group, mappings combine recursively; every other overlapping pair conflicts, even equal scalars | Distinct qualified keys coexist; repeated metadata versions or message sequences conflict within the group |
| Between underlay groups, later values overlay earlier ones; main has higher precedence | Directory order must not accidentally become collection precedence; intentional overrides are separate layers |
| Overlay mappings combine recursively; scalars, sequences, and null replace lower values | An override can replace a setting or message sequence without replacing the entire prompt |
| Syntax and strict-composition failures happen before final domain validation | A higher layer cannot repair a conflicting peer group |
| The configured specification-version path belongs to main; underlays and imported fragments cannot claim it | There is one assembled-configuration specification declaration, not independently checked collection headers |
| Sibling `extends` resolves after composition and precedence | Fully qualified profile keys work as exact cross-collection inheritance references |
| Defaults, typed decoding, resolution, and semantic validation operate on final assembled values | PromptKit need not validate each collection separately |
| Source origins retain resource context and inspection history | File-backed content and explicit provenance inspection can retain existing behavior |
PromptKit already provides directory and embedded-source helpers, and its
configuration adapter calls `configkit.LoadLayered` once. Its current document
schema only exposes `backends`, `profiles`, and `prompts`; this proposal adds
version and metadata fields rather than claiming they work today. The
[current format reference](../formats.md) remains authoritative until adoption.
## Proposed Document Shape
The effective configuration has these application fields. Source documents may
also use supported ConfigKit directives, which ConfigKit consumes before the
application handoff:
| Field | Proposed meaning |
| --- | --- |
| `version` | PromptKit configuration/collection specification identifier, owned by the main input |
| `collections` | Optional mapping from namespace to collection release metadata |
| `backends` | Existing backend definitions, keyed by exact resource ID |
| `profiles` | Existing profile definitions, keyed by exact resource ID |
| `prompts` | Existing prompt definitions, keyed first by exact resource ID and then by prompt version |
The namespace roadmap's accepted grammar applies to resource keys: either an
unqualified ID or exactly `namespace/local-name`. Neither qualified component
contains `/`; dots remain available within local names. Published collections
use qualified keys. Unqualified application definitions remain supported.
Keep IDs in mapping keys, including the prompt's outer key; do not introduce
another embedded `id` or `namespace` field in each definition. Collection
metadata does not prefix or rewrite keys. Filenames and directory names have
no effect on identity. Namespace comparison and references remain exact.
The following compact contribution could be supplied by a provider collection.
The endpoint and model are synthetic configuration examples, not live targets:
```yaml
collections:
provider:
version: "2026.10.14"
backends:
provider/default:
endpoint: https://inference.example.invalid/v1
profiles:
provider/brief:
backend: provider/default
model: vendor/example-model
temperature: 0.2
```
Two independently maintained application collections can then supply the same
local prompt name without colliding:
```yaml
collections:
weatherreporter:
version: "2.4.0"
prompts:
weatherreporter/alerts.summarize:
"1":
default_profile: provider/brief
messages:
- role: user
content: Summarize the weather alerts.
output:
format: text
validation_mode: none
```
```yaml
collections:
homewatch:
version: "3.1.0"
prompts:
homewatch/alerts.summarize:
"1":
default_profile: provider/brief
messages:
- role: user
content: Summarize the household alerts.
output:
format: text
validation_mode: none
```
ConfigKit sees separate keys under `prompts`. It never needs to understand
PromptKit namespaces to preserve both definitions. The quoted `"1"` keys are
individual prompt versions, unrelated to the main specification version or the
collection release strings.
## Specification And Collection Versions
Configure ConfigKit's existing `VersionRule` at `configkit.Keys("version")`.
Recommend requiring an explicit supported value on the selected main input for
this new standard: canonical YAML is `version: 1`. ConfigKit exposes both integer
`1` and string `"1"` as version text `1`; accept both, while documenting the
integer spelling. Reject absent, null, and unsupported versions through the
configured checker. These are proposed PromptKit format choices, implemented
through ConfigKit's supported callback, not a separate input-scanning pass.
A minimal main input is:
```yaml
version: 1
```
Programmatic applications can supply the same main input with `configkit.Mapping`.
Do not infer the specification from a directory name or collection release, strip
version fields from fragments, or silently inject a version to hide a missing
declaration. Requiring main instead of accepting an omitted main is an additional
explicit migration cost of this proposal.
Reusable collection files, including a collection's root metadata file, omit
top-level `version`. So do operator underlays and imported fragments. ConfigKit
protects the configured version path even if a repeated value would equal `1`.
The version checker is invoked by ConfigKit once, at its normal point before
underlay assembly; it selects the contract for the complete configuration.
For collection metadata, propose an ordinary optional `collections` mapping:
- Its keys are exact nonblank namespace names without `/`.
- Each effective entry is a non-null mapping with a required nonblank string
`version`. Quote collection release identifiers; this nested field does not
receive ConfigKit's special integer-to-string handling for top-level `version`.
- Release strings are descriptive publisher identifiers, not executable
dependency constraints or implicit precedence. No SemVer ordering is needed.
- An omitted, null, or empty top-level `collections` mapping means no declared
collection metadata. Null individual entries are invalid final descriptors;
null is not a new per-entry deletion operator.
Validate this metadata only in final effective values. Do not require a metadata
entry for every resource namespace or require every metadata entry to have
resources. This lets ordinary application configuration add definitions or
overrides without fabricating a collection release. Collection publishing
guidance recommends declaring its own namespace and version, but PromptKit
does not audit which source file originally contributed a resource.
## Source Selection And Layering
Recommend this caller-selected assembly:
| Tier | Contents | Relationship |
| --- | --- | --- |
| First underlay group | All selected peer collection inputs | Strict composition across the entire group |
| Later underlay groups, if selected | Application defaults or operator override documents, in caller-chosen priority order | Strict within each group; later groups overlay earlier groups |
| Main input | Specification declaration and optional highest-priority local settings | Overlays the underlays |
These are integration guidance, not a new enforced source policy. The application
still selects ConfigKit groups and precedence explicitly. In particular, separate
directories do not each require their own underlay tier. Append their discovered
inputs into one group when they are independent peer libraries. Putting each
directory into a successive group intentionally enables later overrides.
An illustrative caller arrangement using existing source APIs is:
```go
var peers []configkit.Input
for _, directory := range collectionDirectories {
inputs, err := promptkit.DirectoryInputs(directory)
if err != nil { return err }
peers = append(peers, inputs...)
}
main := configkit.Input{Source: configkit.File(applicationConfigFile)}
sources := promptkit.ConfigurationSources{
Underlays: []configkit.Underlay{{Inputs: peers}},
Main: &main,
}
// Pass sources to NewEngine after the proposed document schema is implemented.
```
The helpers already supply filesystem context, recursively select YAML, and
leave imports disabled. An operator may install a collection with an ordinary
`collection.yml` metadata contribution and definition files in subdirectories.
That filename is a recommended packaging convention, not a special loader
instruction. All selected YAML files participate normally; resource files should
be outside that scan or use suitable non-configuration formats.
For explicit imports instead, select only collection entry-point files, enable
imports on those inputs, and let ConfigKit traverse their fragments. Do not both
recursively select all files and import those same files in the same group:
ConfigKit rejects repeated sources. Imports remain root-contained, relative,
opt-in paths/globs; they neither fetch repositories nor establish precedence.
Each directory input can have its own filesystem root, so peers need not share
an installation directory.
Importing all peers from main is also possible under ConfigKit's rules, but
main's own definitions and its imports then compose strictly. A main document
cannot override something in its own import graph. The recommended underlay
arrangement keeps main available for ordinary application overrides without
requiring a selected-overlay API addition to PromptKit.
## Collisions, Partial Contributions, And Overrides
| Contributions | Current ConfigKit outcome |
| --- | --- |
| `weatherreporter/alerts.summarize` and `homewatch/alerts.summarize` | Both definitions remain |
| Same qualified prompt ID, different prompt-version keys | Both versions remain; PromptKit's existing version selection applies |
| Same ID/version, disjoint definition fields in one group | Fields compose into one definition |
| Same ID/version, overlapping message sequences in one group | `compose.conflict`, even if the sequences are identical |
| Same `collections.<namespace>.version` in two peer inputs | `compose.conflict`, even if release strings are equal |
| Same namespace with disjoint metadata/resource contributions | Contributions can compose; there is no namespace-ownership error |
| Same qualified ID or collection metadata in a later layer | Ordinary recursive overlay semantics apply |
This does not promise that every repeated resource ID or collection namespace
is fatal. ConfigKit permits disjoint mapping contributions by design. Namespace
qualification avoids accidental *cross-namespace* collisions; strict grouping
detects conflicting leaves among peers. Deliberately sharing an exact key in
different precedence layers still allows overrides.
For example, main can override only the selected profile's temperature:
```yaml
version: 1
profiles:
provider/brief:
temperature: 0.1
```
The lower backend and model remain. Replacing `messages` would replace the
entire sequence, not append messages. An empty mapping does not clear inherited
children. Explicit null retains existing PromptKit field-specific meanings;
this proposal adds no generic delete or replace-whole-resource directive.
Do not inspect provenance to reject a higher-priority metadata edit, or declare
that a collection namespace is locked to one input. Provenance is for explicit
operator explanation. Final domain validation can reject an invalid resulting
profile or descriptor, but cannot resurrect or reject displaced source values.
## References, Inheritance, And Resources
Stored references remain exact IDs. A collection prompt can select
`provider/brief`; a profile can inherit `provider/brief` by writing that exact
key as `extends`. Profile mappings remain siblings under `profiles`, so this
uses ConfigKit's existing inheritance without moving definitions into nested
collection containers. A consumer's selected namespace never rebinds those
stored references. Provider `model` strings remain unchanged and may contain `/`.
Prompt versions remain an inner mapping. ConfigKit's sibling inheritance is
generic, but it does not make another prompt's outer ID a sibling of an inner
version definition. This proposal introduces no cross-prompt reference resolver.
Keep existing resource semantics: ConfigKit records the effective field's origin
and source context, and PromptKit uses that context for file-backed prompt
content. Do not derive content paths from namespaces or a collection metadata
filename. Schema roots and runtime artifacts retain their existing separately
configured behavior; a metadata file does not automatically mount schemas or
change their resource resolution. Moving to configuration collections must not
claim broader resource portability than those contracts provide.
## Collection Inventory
Propose a public `ListCollections` operation returning detached descriptors from
the engine's captured effective metadata. Exact Go names and signatures remain
to be chosen. Each descriptor contains namespace and declared collection release
version. Expose the effective specification version once at engine/configuration
level, not as a falsely independently checked version on each collection.
An absent metadata map returns no collection descriptors. Sort by namespace for
stable inspection. Report resources separately through the accepted namespace
listing/filter APIs; unregistered namespaces can exist there without fabricated
collection versions. A metadata-only declaration can appear in collection
inventory even when no resources remain under that namespace.
This API means "effective declared collections," not "every manifest ever
loaded" or "verified installed package versions." Metadata and resources can
be changed by later layers. A declared release does not certify that all effective
definitions are identical to a publisher's release. ConfigKit inspection explains
the sources and edits; the domain API does not replay history or reread files.
## PromptKit Changes And Deliberate Limits
Adoption would add the version field/checker and collection metadata schema,
final-value validation, captured inventory, and the public collection API. It
would also implement the accepted scoped lookup and discovery behavior and
remove automatically bundled catalogs and built-in profiles, as tracked in the
parent roadmap. Update canonical contracts and migration guidance only when
those changes land. Existing documents with no main specification declaration
would need an explicit migration under the proposed version policy.
There is no ConfigKit change, per-collection loading/validation pass, source-level
namespace registry, key-rewriting stage, or automatic configuration installation.
Definitions retain their qualified keys throughout assembly. Invalid effective
domain values are PromptKit's responsibility; composition, imports, inheritance,
and version-path protection remain ConfigKit's responsibility under
[ADR 0001](../adr/0001-configuration-boundary.md).
The main limitation is independent specification compatibility: a reusable
collection cannot carry its own protected top-level `version: 1` when imported
or used as an underlay. Publishers/operators must select configuration compatible
with the main standard. Strict decoding catches unsupported surviving fields,
but it is not an independent compatibility check for each collection release.
This is the principal issue to compare with proposal 2.
## Validation And Review Criteria
Review verification on 2026-10-14: the focused ConfigKit composition, grouped
loading, and import-version tests passed in the local checkout. A temporary
offline consumer using that checkout also loaded the five YAML examples above
through a prospective document schema and verified distinct identities, equal
and unequal peer conflicts, disjoint contributions, main overrides, atomic
sequence replacement, main/underlay/import version ownership, and qualified
inheritance. This establishes feasibility with current ConfigKit; it does not
implement the proposed PromptKit schema or inventory API. No temporary harness
or module replacement is part of either repository.
Review this proposal against these observable cases:
- Two collections using the same local prompt name survive composition under
distinct qualified keys.
- Equal and unequal overlapping metadata versions fail in one peer group;
disjoint contributions compose, and later metadata overrides remain allowed.
- Main overrides one profile field while preserving other fields; sequence
overrides replace whole sequences.
- A strict peer conflict cannot be hidden by a higher layer.
- Main `version: 1` succeeds, while omitted/null/unsupported versions fail the
proposed checker and imported/underlay declarations fail ConfigKit's existing
version ownership checks.
- Fully qualified inheritance resolves through the assembled sibling mappings;
file-backed content retains its declaring source's context.
- Inventory reflects final metadata and remains captured when source files
change; resource namespace discovery does not require a metadata declaration.
The configuration schema, explicit-version requirement, metadata null rules,
inventory shape, and recommended peer-group layout are proposal choices to
review. They do not resolve the parent roadmap's decision 5 merely by being
written here.

View File

@@ -485,6 +485,10 @@ dependencies instead.
### 5. Collection Metadata And Specification Versioning (Open)
[Proposal 1: current ConfigKit](configuration-collections-current-configkit.md)
develops a concrete configuration standard for the existing composition rules.
It is a proposal for comparison; this decision remains open.
**Desired capability:** describe collection namespace and release version in
configuration and expose an API through which downstream applications can list
effective collection declarations captured by an engine. A collection release