28 KiB
Proposal 3: Collections With Mounted Configuration Fragments
Status: proposed PromptKit standard; not implemented in PromptKit. Required
ConfigKit capabilities are implemented on its development branch, reviewed at
e66c371 on 2026-10-16, but are not in PromptKit's selected v0.7.1 dependency.
The recommended direction uses collection-local mapping keys, one installed
definition per prompt, and an optional flat prompt version field. The latest
review agrees on shared source checks using IfPresent and a distinct collection
release_version field; remaining PromptKit decisions are listed below. Compare
proposal 1 and
proposal 2; the parent
namespace roadmap owns the shared decisions.
Recommendation
Use ConfigKit's implemented input destinations and relative import destinations.
For example, a collection entry can import its prompts directory beneath
[collections, weatherreporter, prompts]. Authors write local prompt names and
definitions without repeating either prompts: or the namespace in each file.
Use a collection-shaped effective configuration. Each collection contains its release metadata and ordinary mappings of backends, profiles, and prompts. PromptKit derives qualified public IDs from the final collection and resource keys after ConfigKit hands off the resolved configuration.
There is one installed definition per qualified prompt ID. Its optional
version field describes that definition; it does not create another identity
or select a merge strategy. This removes the need for proposal 2's identified
record sequences and for the current nested prompt-version mapping.
Placement belongs to ConfigKit. PromptKit must not parse, wrap, or rewrite each source itself, nor impose duplicate-namespace checks before composition. This preserves the accepted configuration boundary.
Reviewed Upstream Capabilities And Adoption Status
The reviewed ConfigKit development checkout is e66c371. Its public
configuration contract (docs/config.md, sections “Input destinations” and
“Shared source-version checks”) and ADRs 0011–0013 own the upstream behavior.
PromptKit still selects ConfigKit v0.7.1; choose a released dependency containing
these capabilities before implementation. This roadmap does not imply they are
already available through PromptKit or select an upstream release number.
The implemented public surface provides:
Input.Destinationfor root-relative placement, and YAML importdestination: [prompts]for placement relative to the immediate importing document. Glob matches share the declared destination; nested imports append to their immediate parent's destination.Options.SourceVersionModeandLayeredOptions.SourceVersionMode, using one sharedDefinition.Versionrule. Enabled modes interpret the rule's path in each document's source-local coordinates before placement and composition.- Consumption of accepted non-main specification headers, with the main header retained and protected at its effective application path.
SourceInfo.DestinationandSourceInfo.VersionCheckfor detached inspection evidence, alongside existing source locations and resource contexts.
Strict mapping composition, explicit precedence, atomic sequences, and local sibling inheritance retain their existing rules. ConfigKit owns the complete load; PromptKit interprets and validates final effective values. No further ConfigKit extension is required for the initial direction described here.
The new PromptKit schema and runtime indexes remain future work. The existing format reference remains authoritative until adoption.
Authoring And Assembly
A file such as weatherreporter/prompts/alerts.yml would contain:
summarize.alerts:
version: "1"
default_profile: provider/brief
messages:
- role: user
content: Summarize the weather alerts.
output:
format: text
validation_mode: none
The version may simply be omitted. Another collection can contribute the same
local prompt name, for example homewatch/prompts/alerts.yml:
summarize.alerts:
default_profile: provider/brief
messages:
- role: user
content: Summarize the household alerts.
output:
format: text
validation_mode: none
A provider collection's profiles/brief.yml uses the same local-key convention:
brief:
backend: provider/default
model: vendor/example-model
temperature: 0.2
Its backends/default.yml could contain this synthetic, non-live endpoint:
default:
endpoint: https://inference.example.invalid/v1
A conventional weatherreporter/collection.yml entry can declare the shared
specification, optional collection release, and its layout:
version: 1
release_version: "2.4.0"
configkit:
imports:
- glob: prompts/*.yml
destination: [prompts]
A provider entry can import profiles/*.yml at [profiles] and
backends/*.yml at [backends] in the same way. Collection publishers choose
which categories to include. A required glob must match; unused categories can
be omitted or use ConfigKit's existing optional-import behavior. Ordinary
fragments may omit specification headers under the selected IfPresent mode.
An entry header is the recommended publishing convention, not a load-time
requirement selectively enforced on entries by this mode.
The application selects each entry, enables its imports, and supplies its namespace through the public input destination. For example, the following partial Go snippet uses implemented ConfigKit development-branch APIs:
entry := configkit.Input{
Source: configkit.File("/usr/local/share/collections/weatherreporter/collection.yml"),
Imports: true,
Destination: configkit.Keys("collections", "weatherreporter"),
}
Put peer collection entries into one Underlay group's Inputs. Configure
LayeredOptions.SourceVersionMode as SourceVersionModeIfPresent and
Definition.Version.Path as configkit.Keys("version"), with PromptKit's
supported-specification checker. The application main input remains at the
root and declares version: 1 in the examples below. Namespace selection and
source selection require no manifest parsing in PromptKit.
| Selected source | Effective destination |
|---|---|
| Application main document | Configuration root |
weatherreporter/collection.yml |
[collections, weatherreporter] |
Its prompts/*.yml imports |
[collections, weatherreporter, prompts] |
| Homewatch entry and its prompt imports | [collections, homewatch] and [collections, homewatch, prompts] |
| Provider entry and its profile/backend imports | [collections, provider], then [profiles] or [backends] appended |
Directly selected fragments with Input.Destination remain possible. Entry
imports are the recommended packaging convention because publishers can describe
their layout. Filenames and directory basenames do not define resource identity;
no implicit directory discovery, namespace inference, or collection installer is
introduced. A helper may construct inputs from explicit source selections but
must not inspect manifests to compute their destinations.
Use later underlay groups or main for intentional application/operator overrides. Placement does not establish precedence. Do not also recursively scan files already selected by entry imports: ConfigKit's repeated-source rules still apply.
The entry's source-root version is checked and consumed before its payload
composes. Its release_version is retained as ordinary collection metadata.
A prompt's nested optional version is also ordinary payload. These are three
distinct concepts; source-root release metadata must no longer use version.
The assembled application shape would be equivalent to:
version: 1
collections:
weatherreporter:
release_version: "2.4.0"
prompts:
summarize.alerts:
version: "1"
default_profile: provider/brief
messages:
- role: user
content: Summarize the weather alerts.
output:
format: text
validation_mode: none
homewatch:
prompts:
summarize.alerts:
default_profile: provider/brief
messages:
- role: user
content: Summarize the household alerts.
output:
format: text
validation_mode: none
provider:
backends:
default:
endpoint: https://inference.example.invalid/v1
profiles:
brief:
backend: provider/default
model: vendor/example-model
temperature: 0.2
This yields weatherreporter/summarize.alerts and
homewatch/summarize.alerts, plus profile provider/brief and backend
provider/default. The provider's model string remains vendor/example-model;
it is not a PromptKit resource identifier.
One fragment may contain several local definitions. A fragment starting directly
with messages: would need a destination that also supplies the local prompt
key. ConfigKit mounting permits such placement, but with source checks enabled
its root version would be the specification header, not prompt metadata. Recommend
the local-key mapping above as the standard authoring form: identity travels
with the definition and files may be reorganized without changing it. Filename-derived IDs are not
needed.
Final Schema And Identity
Recommend collections as a mapping of namespace to collection body, containing
optional release_version and optional backends, profiles, and prompts
mappings. Collection release labels are descriptive, with no SemVer ordering
or dependency-solving semantics. Metadata is optional: an installed collection
can contain resources without claiming a published release.
Each resource key inside a collection is local. The parent namespace and local
key follow the accepted slash-free component grammar; their public spelling is
exactly namespace/local-name. There are no duplicate embedded id or
namespace fields to reconcile. Dots remain ordinary characters.
To retain unqualified application resources, recommend optional root-level
backends, profiles, and prompts mappings, also keyed by local name and
using the same definition shapes. They produce unqualified public IDs. Under
this proposal, namespaced definitions belong under collections, not a second
qualified-key spelling in those root maps. This avoids two configuration paths
producing the same public identity. This representation is a recommendation
for proposal 3, not a change to today's accepted format.
Stored references such as default_profile, profile backend, and smoke-test
profile defaults remain exact public references. Nesting does not automatically
qualify a slashless reference or rebind it to caller scope. For example, a
profile in provider still declares backend: provider/default. Caller-scoped
lookup follows the existing namespace roadmap after final indexes are built.
One Installed Prompt And Optional Version
The resource's qualified ID is its sole configuration identity. There is no
version-key mapping, implicit latest, version ordering, or parallel installed
history. Updating the optional version changes metadata on the same prompt.
Recommend a nonblank opaque string when a prompt version is supplied, with no
SemVer constraint. Omission or explicit null means no declared version; null
can clear a lower-layer or inherited label. Quote numeric-looking labels such
as "1". Do not synthesize a version for an unversioned prompt. These detailed
value rules are recommendations; the agreed requirement is that the field be
optional.
Execution and inspection without a requested version select the installed prompt directly. As an API migration recommendation, retain an optional requested version as an exact assertion against the installed label: a match succeeds; a mismatch or an unversioned definition fails the assertion. It never searches for another version. Removing request-side version fields altogether is a simpler alternative but requires more consumer changes; settle the public API shape before implementation. Listings should identify one prompt per ID and expose the optional declared version separately from identity.
This intentionally supersedes multi-version preservation for this alternative. Proposals 1 and 2 retain their earlier assumptions for comparison. The parent roadmap's version-selection and listing language needs corresponding revision if proposal 3 is adopted.
ConfigKit Composition Boundary
Use the implemented upstream contracts without duplicating them in PromptKit.
Input destinations use literal mapping-key segments, independent of filesystem
roots and precedence. Relative import destinations are sequences of strings;
omission and [] retain the importing destination. Null, scalar, and non-string
destination members are invalid. No logical path segment means parent traversal
or a reset to the application root.
Source-root import directives remain structural. Imported content contributes at
its resulting destination, while physical import paths and content_file bases
remain relative to their declaring sources. SourceInfo.Destination records
placement; normal origins and diagnostics retain real source locations. Selected
overlays and explicit CLI overrides remain rooted at the application root.
Mounting preserves source identity, cycle/repetition rules, and load limits.
An absent NoSource contributes no payload; an actual empty mapping can
materialize its destination containers without clearing lower contents.
ConfigKit's reserved structural keys continue to apply. PromptKit must not
strip headers, re-key source trees, merge fragments itself, or turn source
inspection into an additional enforcement pass.
Composition And Overrides
Different collection keys prevent accidental collisions between equal local resource names. Two sources targeting the same namespace are not automatically invalid: disjoint mapping contributions combine. Overlapping scalar fields, message sequences, nulls, and equal release labels conflict within a strict group. Across deliberate layers they follow ordinary precedence instead.
A higher-layer prompt mapping is a partial override. Changing only its version does not replace its messages or other settings. Omitting a field retains its lower value, and an empty mapping does not clear a definition. Publishers and operators must treat such changes as configuration customization, not as an atomic installation of another prompt release. Do not introduce source audits or a version-triggered replacement rule to conceal this distinction.
Inheritance
Existing sibling inheritance remains useful within each collection's resource
mapping: extends: brief can name the local profile brief. Prompt versions,
now ordinary optional fields, would inherit and overlay like other fields;
version: null explicitly clears an inherited label.
Cross-collection profiles are no longer immediate siblings. Consequently,
extends: provider/brief cannot reach that collection under current ConfigKit
inheritance. Recommend local sibling inheritance for the initial proposal;
applications can still execute or select the qualified provider/brief
profile directly. Migrating existing cross-collection inheritance requires
flattening those authored definitions or making a separate upstream change.
An alternative is generic explicit-path inheritance in ConfigKit, with its own syntax, cycle rules, and provenance contract. That would preserve reusable cross-collection bases, but crosses a currently deferred ConfigKit boundary and is additional work beyond mounting. PromptKit must not resolve such relationships itself after handoff. Decide whether this capability is required before selecting a migration plan.
Specification Versions And Collection Inventory
Shared Specification Policy
Use one Definition.Version policy, with source-local path version, for the
candidate PromptKit specification. Initially accept specification text 1 from
string or strict decimal-integer declarations; reject explicit null and other
unsupported declarations through the ConfigKit checker. The handling of an
absent main declaration remains an explicit decision below. Accept additional
versions only when PromptKit actually supports their format semantics: accepting
a header does not migrate its payload or independently validate a fragment's
complete schema. The three proposals are alternative candidate formats, not
interchangeable released meanings of specification version 1.
The selected initial direction is SourceVersionModeIfPresent:
| Mode | Implemented behavior | Role in this proposal |
|---|---|---|
Default |
Existing main-only effective-path check and protected-path behavior | Not the proposed shared-source policy |
IfPresent |
Checks present declarations; skips absent non-main declarations | Initial choice for compact fragments |
Required |
Requires declarations on every participating document | Possible stricter consumer option; not entry-only checking |
Participating documents include actual main and underlay inputs, transitive and glob imports, and the selected overlay. Defaults, synthetic CLI overrides, resolution output, absent optional imports, and empty groups do not need headers. Explicit null is present and reaches the checker; malformed selected values fail ConfigKit's version-type validation. A rejected declaration fails loading even if a higher layer would supersede that source's payload.
In enabled modes, accepted non-main headers are consumed before composition.
The main declaration remains and is protected at its destination-prefixed path.
PromptKit's recommended main is unmounted, so that protected path is /version.
Do not confuse header consumption with permission to override the remaining
protected application field.
IfPresent does not require a collection entry header and does not infer a
fragment's compatibility from its entry. It checks the declarations actually
present. Requiring headers only on entry files is not supported by the load-wide
mode. Do not emulate it using a preliminary manifest load or validation of
source history. If that stronger guarantee becomes essential, seek an explicit
upstream contract instead.
Naming And Optional Main Inputs
Reserve source-root version for specification declarations throughout the new
format. Use release_version for optional collection release labels and retain
nested prompt version for the optional installed-prompt label. An old release
file containing only version: "2.4.0" can fail the specification check; if its
text happens to be accepted, that field is consumed and lost as release metadata.
Migration must rename it rather than simply enable the new mode.
This reservation affects resource fragments whose local key is literally
version: a root mapping with that name is interpreted as a malformed header.
Recommend expressing such a resource within a nested resource mapping in the
collection entry, for example prompts: {version: ...}, rather than globally
banning that public resource ID. The exact author-facing convention remains
open below. Existing configkit and extends structural reservations also
remain applicable; destinations do not bypass them.
An absent header in an actual main document still reaches the checker under
IfPresent. NoSource also invokes the existing absent-main checker behavior,
even under Required. Consequently, the mode alone does not determine whether
PromptKit supports construction without a main declaration. Recommend an
explicit main specification declaration for the new standard, including for a
programmatically supplied main, without silently injecting one. An alternative
is to accept absent main declarations deliberately for underlay-only callers;
settle that compatibility choice before implementing the checker.
Inspection And Effective Collection Inventory
SourceInfo.VersionCheck is source evidence, not a generic success flag. A
non-nil record with Present == false can describe an omission for which the
checker did not run. Header paths and locations are source-local. Evidence is
detached and does not reread inputs; text is conservatively redacted during
callbacks and on failed loads, and successful final records respect completed
sensitivity classification. These records belong in explicit configuration
inspection, not in ordinary domain validation or collection-release identity.
Derive collection inventory from the final collections mapping: namespace
plus optional effective release_version, sorted and detached from the captured
engine. Metadata-only and resource-only entries can both appear. Root unqualified
resources do not invent a named collection; resource-based namespace enumeration
remains separate. Exact exported inventory types remain API design work.
An inventory describes effective configuration, not immutable package identity, historical source ownership, or proof of unchanged published contents. Operator overrides may change resources while retaining a release label. Engine construction captures definitions; edits require a replacement engine.
Comparison And Migration
| Concern | Proposal 1 | Proposal 2 | Proposal 3 |
|---|---|---|---|
| Author identity | Qualified mapping key | Namespace/local-ID fields | Local mapping key beneath a mounted collection |
| Prompt version | Required nested key | Required identity field | Optional descriptive field |
| Multiple installed versions per prompt | Supported | Supported | Not supported |
| Repeated resource wrapper in fragments | Required | Required | Omitted through placement |
| Composition machinery | Existing mappings | Identified record sequences | Existing mappings after placement |
| Upstream prerequisite | None beyond its original baseline | Proposed record composition and inheritance | Placement, relative imports, and shared source checks implemented in development |
| Cross-collection inheritance | Existing qualified sibling keys | Proposed tuple references | Requires a separate extension or authored flattening |
| Source specification checks as described | Main only | Main only | Shared policy; non-main declarations checked when present |
Proposals 1 and 2 retain their original assumptions for historical comparison. Proposal 3 is the recommended direction for the stated one-installed-prompt requirement. Its authoring economy follows from the configuration hierarchy, and it retains ordinary map composition. The tradeoffs are explicit assembly destinations, changed inheritance scope, and a deliberate reduction of prompt version selection to one installed definition.
Migration must choose one installed definition for each former multi-version
prompt, move any retained version into its optional field, and relocate
qualified resource keys into collection/local-key mappings. Update destination
declarations, stored references, request-version handling, inventories, and
configuration-inspection paths. Rename collection release metadata to
release_version, configure the shared checker and mode, and account for the
source-root header reservation and the selected main-absence policy. Preserve
provider model strings and source resource bases. Namespace mounting does not
automatically relocate schema or artifact stores or rewrite references when a
collection is renamed.
Retain the accepted removal of mandatory catalog modules and built-in profiles. This is a breaking configuration/API change under the release policy; implementation must include consumer migration guidance and update current contracts when it lands. The proposal does not change today's supported configuration or API.
Delivery And Validation
Select a released ConfigKit dependency containing the reviewed capabilities, then implement PromptKit's schema, source-selection convenience APIs, and final configuration adapter. Do not use a committed filesystem replacement as the release integration. Build qualified runtime indexes after handoff. Collection installation, remote fetching, implicit discovery, live reload, identity-based list composition, and dependency solving remain outside this work.
PromptKit acceptance should exercise typed assembly of multiple collections, relative/nested imports, source-local header checking and consumption, ordinary release and prompt metadata, unqualified resources, scoped selection, optional prompt versions, inventory without release metadata, source-relative content, intentional partial overrides, and the chosen main-absence policy. Add request version assertions if retained. Do not duplicate ConfigKit's generic source validation or enforce domain rules through historical source inspection.
Review verification on 2026-10-16 against ConfigKit e66c371: focused public API
and consumer-example tests passed. A temporary typed consumer also passed
combined assembly for two collections with equal local prompt names, nested
relative imports and content-file contexts, optional prompt versions, both
enabled header modes, rejected headers despite upper-layer overrides, and
migration behavior for old release fields. The temporary consumer lives outside
both repositories. An additional offline check assembled this document's YAML
fragments through entry imports and matched the illustrated effective tree;
local links and whitespace checks passed. These checks verify upstream behavior
and the candidate schema, not an implemented PromptKit engine feature.
Remaining PromptKit Decisions
The agreed direction uses the implemented mount/import APIs, a shared
specification policy with IfPresent, and distinct release_version metadata.
These are no longer pending upstream feature requests. The following choices
remain before the PromptKit contract is complete:
- Main declaration: recommend requiring an explicit supported declaration in a real or programmatic main input. Alternatively, accept absent main for existing underlay-only callers, with the weaker guarantee documented. Neither option follows automatically from ConfigKit's mode, and no version should be silently injected.
- Public mode selection: use
IfPresentinitially. Decide whether callers may opt intoRequiredthrough PromptKit's source configuration API. Exposing it gives deployments an all-document declaration requirement; keeping one initial mode reduces API surface. It cannot mean entry-only enforcement. - Reserved header naming: recommend allowing a local resource ID
versionthrough nested mappings in the entry document while reserving source-rootversionfor the specification. Alternatively, reserve that resource name in the new format for a uniform fragment convention, at the cost of a new ID restriction. Do not let an incidental parser limitation silently decide it. - Request versions: recommend retaining optional exact assertions against the one installed prompt's label. Removing request-side version fields is simpler if consumers do not need this compatibility guard. Also finalize the proposed absent/null/nonblank-string rules and listing representation.
- Cross-collection inheritance: recommend local sibling inheritance and flattening authored cross-collection bases for the initial migration. Request generic cross-path inheritance upstream only if concrete catalog migration needs justify it; do not resolve it inside PromptKit.
- Consumer API and packaging: recommend an explicitly selected entry path and namespace, with publisher-owned relative imports and one peer group. Finalize the helper signatures, conventional entry filename, inventory types, and migration examples. Direct mounted fragments remain a useful alternative for applications that already assemble sources themselves.
The parent roadmap still owns the overall configuration decision. Its older version-selection and metadata examples describe earlier alternatives; adopting this standard requires reconciling those details and updating current contracts when implementation lands.