Files
ConfigKit/docs/roadmap/promptkit-feature-request.md

501 lines
27 KiB
Markdown

# PromptKit Feature Request: Mounted Collections And Specification Headers
Status:
- Input destinations are implemented on the ConfigKit development branch
(placement baseline reviewed at `315f464`).
- Relative import destinations have been accepted and implemented; the
[roadmap](relative-import-destinations.md) records their completed delivery.
- Source specification headers have been partially accepted in the shared
application-version-policy form described below. This does not accept
PromptKit's original independent collection-header semantics. Design and
implementation of this addition remain separate from relative import work.
## Requested Capability
Extend configuration loading with relative import destinations and opt-in
checks that contributing sources declare a version accepted by the consumer's
single configuration specification policy. Preserve the implemented capability
to select a destination mapping path for each configuration input. The input's
application content contributes
beneath that path while retaining ConfigKit's normal composition, precedence,
inheritance, validation, diagnostics, and inspection behavior.
PromptKit needs this capability for its proposal 3, **Collections With Mounted
Configuration Fragments**, recorded in PromptKit's
`docs/roadmap/configuration-collections-mounted-fragments.md`. This request
describes the public API needs and observable acceptance criteria. It does not
prescribe ConfigKit's internal design or an implementation plan. The disposition
and revised source-version scope below supersede conflicting parts of PromptKit's
original proposal.
The original request targeted ConfigKit v0.7.1. The development branch now
supplies `Input.Destination` and `SourceInfo.Destination`, as documented in the
[input destination contract](../config.md#input-destinations). The placement
requirements below remain the baseline for the two additions. Current released
behavior and development-branch behavior must not be confused with the newly
proposed header and import syntax.
## Consumer Problem
PromptKit applications assemble independently maintained collections of prompts,
inference profiles, and backends. Collections can contain identical local names,
such as `summarize.alerts`, without identifying the same resource. PromptKit wants
an effective configuration organized by collection, while letting authors omit
repeated collection and resource-container wrappers from individual files.
A file selected from a collection's `prompts` directory would contain:
```yaml
summarize.alerts:
version: "1"
default_profile: provider/brief
messages:
- role: user
content: Summarize the weather alerts.
output:
format: text
validation_mode: none
```
Given destination `[collections, weatherreporter, prompts]`, its contribution
would appear in the final application configuration as:
```yaml
collections:
weatherreporter:
prompts:
summarize.alerts:
version: "1"
default_profile: provider/brief
messages:
- role: user
content: Summarize the weather alerts.
output:
format: text
validation_mode: none
```
Another collection can contribute `summarize.alerts` beneath
`[collections, homewatch, prompts]` without conflicting. After final handoff,
PromptKit interprets these as `weatherreporter/summarize.alerts` and
`homewatch/summarize.alerts`. The profile reference is illustrative and must be
supplied separately by the application configuration.
PromptKit will support one installed definition per prompt. Its optional flat
`version` is ordinary domain metadata, not a merge identity. This request does
not need identified-record sequences, sequence-root documents, or a custom
merge policy.
Authors could write the complete wrappers today, but that repeats assembly
details in every fragment. Consumer-side parsing and wrapping would put generic
source processing back into PromptKit and complicate source attribution.
PromptKit instead wants to select sources and destinations, then receive one
final configuration through ConfigKit's supported loading lifecycle.
## Implemented Placement API And Required Baseline
### Per-Input Destination
The development branch provides `Destination Path` on each `Input` passed to
`LoadLayered`, matching the original request. The following partial consumer
example uses that implemented field, which was not available in v0.7.1:
```go
promptInput := configkit.Input{
Source: configkit.File("/usr/local/share/collections/weatherreporter/prompts/alerts.yml"),
Destination: configkit.Keys("collections", "weatherreporter", "prompts"),
}
peers := configkit.Underlay{
Inputs: []configkit.Input{promptInput},
}
```
The consumer would include `peers` in its existing `LayeredOptions` and invoke
`LoadLayered` with its normal root main input and application `Definition`.
This is a partial API sketch, not an executable example or a proposed new
loading entry point. A single public layered load is sufficient for PromptKit;
it does not require a separate mounting operation on completed results.
The API must let the consumer:
- Express a destination relative to the application configuration root using
literal mapping-key segments. Dots, slashes, and numeric-looking text inside
a key are not implicit path separators or array indexes. PromptKit's namespace
grammar remains its own domain rule.
- Omit the destination to retain existing root placement. An explicitly empty
destination should have the same meaning. Mapping destinations are sufficient;
sequence-index destinations are not needed by this request.
- Assign different destinations to inputs in the same strict composition group,
and assign the same destination to inputs in separate precedence groups.
- Use the destination with existing content-bearing source kinds: host files,
native filesystem files, bytes, readers, and mapping sources, subject to their
existing acquisition and ownership contracts.
- Retain the existing import opt-in and filesystem context independently of the
destination. A configuration destination is not a filesystem root or base
directory.
PromptKit can continue to enumerate an explicitly selected directory and attach
the same destination to its returned inputs. ConfigKit does not need a new
directory scanner, namespace registry, filename convention, or PromptKit schema.
The namespace comes from the application's assembly declaration, not from an
implicit interpretation of a directory basename.
### Representative Assembly
These are application choices, not ConfigKit-specific reserved paths:
| Selected inputs | Destination key segments |
| --- | --- |
| Application main document | Root |
| `weatherreporter/collection.yml` | `collections`, `weatherreporter` |
| YAML files under `weatherreporter/prompts` | `collections`, `weatherreporter`, `prompts` |
| YAML files under `homewatch/prompts` | `collections`, `homewatch`, `prompts` |
| YAML files under `provider/profiles` | `collections`, `provider`, `profiles` |
| YAML files under `provider/backends` | `collections`, `provider`, `backends` |
Peer collection contributions belong to a single strict underlay group when the
application wants conflict detection among them. Intentional operator overrides
use later groups or main. Destination selection must not establish precedence
or make source traversal order authorize replacement.
## Required Observable Behavior
### Composition And Precedence
Mounted and explicitly wrapped contributions must have equivalent application
values and conflict behavior at their effective destinations. Actual source
locations naturally differ and must remain accurate.
Retain the [strict composition](../config.md#strict-document-composition) and
[layering](../config.md#layered-loading) rules:
- Disjoint mapping entries at the same destination combine.
- Overlapping non-mapping values within a strict group conflict, including equal
scalars, nulls, and sequences. Different destinations do not collide merely
because the source documents share a local key.
- Later precedence layers recursively override mappings and replace other
values. A strict conflict cannot be repaired by an upper layer.
- Empty mappings do not clear lower definitions; ordinary arrays remain atomic.
In particular, `messages` does not acquire sequence-merging behavior.
- Overlapping destination paths and contributions at their ancestors follow
those same rules. No namespace-level uniqueness restriction is requested.
Changing only a prompt's `version` in a later layer changes that field while
retaining omitted lower-layer fields. ConfigKit must not treat a domain version
label as a request to replace an entire definition.
### Imports And Existing Structural Directives
An input with imports enabled must be able to use its existing source-root
`configkit.imports` directive while contributing application data at a nested
destination. The directive remains structural configuration, not an application
field below that destination.
When an import omits `destination` or supplies an empty list, it inherits its
importing document's effective destination, including transitively. An explicit
sequence appends to that destination. Import paths continue to resolve relative
to their declaring source's filesystem context. A mounted document must not send
its imported application fields back to the configuration root accidentally.
Existing opt-in, containment, missing-input, cycle, and repeated-source behavior
must remain applicable. A different destination is not a request to bypass the
current rejection of a repeated physical source within a strict group.
Sibling `extends` must resolve against mappings at their effective destinations.
For example, two independently supplied profile fragments mounted beneath the
same collection's `profiles` mapping can establish `extends: brief` once they
compose. Inheritance remains ConfigKit-owned; PromptKit does not perform another
relationship-resolution pass.
### Protected Specification Paths
The distinction between source-local field names and effective application
paths is essential. PromptKit's main document will declare `version: 1` at the
configuration root. A separate collection metadata file can contain:
```yaml
version: "2.4.0"
```
Mounted at `[collections, weatherreporter]`, that field is an ordinary collection
release label. It must not be rejected as an attempt to contribute the protected
root specification field. The same applies to a prompt's nested optional
version. This describes the existing placement baseline, with no new source
checks enabled. The partially accepted extension will reuse the application's
[VersionRule](../config.md#application-schema-versions) for source declarations;
header lookup and its interaction with ordinary version payload remain design
questions, not a guarantee that every source-local `version` stays unchecked.
Conversely, placement must not permit an underlay or imported fragment to claim
or replace the actual protected specification path or an ancestor that would
replace it. The public contract must define protection in terms of effective
application paths, rather than reserving the spelling `version` in every source.
Protection must remain valid with imports, inheritance, overlays, and explicit
CLI overrides.
Retain existing behavior when the new source checks are not enabled. The
accepted direction extends the same application version policy across source
files, rather than assigning separate policies to collections. Its interaction
with protected-path ownership and declaration retention must be designed
explicitly; it does not implicitly authorize version-field contributions.
PromptKit does not need main mounted away from the root or integer-to-string
conversion for every nested ordinary `version` field.
### Typed Loading, Diagnostics, And Inspection
The destination must be visible consistently through the ordinary public
loading and inspection surface:
- Final typed configuration and application callbacks use effective destination
paths. Defaults, resolution, and validation retain the existing single-load
lifecycle; fragments are not independently validated as whole applications.
- Metadata and presence queries distinguish omission, explicit null, and other
explicit values at the mounted paths.
- Diagnostics identify the effective conflicting or invalid path and the real
contributing source locations. Consumers can determine which source and
placement produced a value without recreating that relationship themselves.
- Effective rendering, explanations, history, and configuration comparison agree
with the final configuration. Existing sensitivity and disclosure rules apply
to mounted fields, including application path-based secret classification.
- Unsupported destination forms and destination-related loading failures use
the normal structured error boundary. Exact new diagnostic codes, if needed,
are for ConfigKit to define.
PromptKit does not require a separate mount registry or new inspection facade.
Existing inspection APIs are sufficient if they expose accurate effective paths,
source attribution, and placement information. ConfigKit should document how
source-local locations relate to effective paths.
### Source-Relative Resources And Lifecycle
For a mounted field such as `content_file: ./summary.md`, obtaining its effective
origin and calling `Result.SourceContext` or `Resolution.SourceContext` must
still identify the declaring source's filesystem and base directory. The same
holds when that field survives an override or is inherited into another mapping.
Its configuration destination must not change the meaning of the relative file
reference.
Retain existing source ownership, load completion, result lifetime, containment,
and resource-access behavior. Destinations must participate in documented load
limits without creating a way to bypass them; this request does not prescribe
new limit values or their accounting machinery.
## Partially Accepted Addition: Shared Source Specification Checks
Status: the shared-policy portion below is implemented. Current behavior is
defined by the [configuration contract](../config.md#shared-source-version-checks),
and the accepted decision and implementation evidence are in
[ADR 0013](../adr/0013-shared-source-version-checks.md) and the
[source-version roadmap](source-version-checks.md).
### Accepted Scope
A ConfigKit consumer defines one configuration specification, which may evolve
through versions. `Definition.Version` identifies the declaration path and the
consumer's checker determines which versions are acceptable. The accepted
extension lets the consumer apply that same policy across the source files
contributing to a configuration, rather than checking only the main declaration.
It does not introduce independent collection or subtree specification policies.
The consumer can opt in with increasing strictness:
1. **Current behavior:** retain existing loading and version-checking behavior,
including existing protected-path restrictions. No new source-header check.
2. **Check declarations when present:** a source that declares a version must
satisfy `Definition.Version`; omission is allowed.
3. **Require declarations:** every participating source must declare a version
that satisfies `Definition.Version`; omission is an error.
Acceptance is determined by the existing consumer-supplied checker, not by a
new ConfigKit-owned expected string or automatic equality with the main file's
declaration. A consumer may accept several compatible versions. A declaration
rejected under the selected mode fails the attempted load through structured
errors, with no usable partial result; a higher-precedence payload cannot turn
an incompatible source declaration into an acceptable one.
This establishes that checked sources declare an accepted specification version.
It does not prove that every fragment conforms to its declaration. Existing
final decoding and application validation still determine the validity of the
assembled configuration; fragments are not independently loaded as complete
applications.
### Difference From PromptKit's Original Proposal
PromptKit originally requested explicitly selected collection-entry rules,
separate from `Definition.Version`, with ordinary imports exempt from independent
checks. It also recommended consuming accepted entry headers and exposing them
as source evidence while leaving imported release metadata untouched.
The independent collection-entry policies and exemption for ordinary imports
were not accepted. The accepted direction instead uses one shared
`Definition.Version` policy and optional or required declarations across
participating sources. Separately versioned subsets or collections remain
outside this addition's scope. Header consumption and sanitized source evidence,
which were suggestions in the original proposal, were subsequently accepted for
this shared-policy design and are implemented as described by the linked
contract.
### Design Questions Resolved by the Accepted Roadmap
The following questions were open in the original request and are resolved by
the accepted roadmap and ADR 0013:
- How the configured declaration path is interpreted for individual sources,
particularly before destination placement.
- Which source kinds and roles participate, including direct underlay inputs,
transitive imports, and other contributions.
- Whether checked declarations are consumed or retained, how that interacts
with composition and main-owned version protection, and what inspection
evidence is exposed.
- How null and malformed declarations behave in each mode, preserving the
distinction between absence and a present declaration and considering the
existing string/decimal-integer version vocabulary.
- The opt-in API, callback lifecycle, and source-local diagnostic attribution.
In particular, an imported `version: "2.4.0"` at the configured source-local
header path is checked and consumed in an enabled mode. Consumers that need an
ordinary release label retain it at a different path. No automatic field
consumption applies in the default mode, and no new coercion or per-collection
checker is part of the implemented design.
## Requested Addition: Relative Import Destinations
Disposition: accepted and implemented under the completed
[relative import destinations roadmap](relative-import-destinations.md).
The behavior below records the accepted capability; exact syntax and completion
status follow that work, rather than implying a released feature.
Extend the public YAML import directive so a `path` or `glob` import can specify
a relative destination as literal mapping-key segments. The roadmap selects
`destination` as a sequence of strings, for example `destination: [prompts]`.
The public behavior should be:
- The importing document has an effective destination established by its input
or its own import. An explicit import destination appends key segments to that
destination. Omission or an empty list retains the importing destination.
- Every file matched by a glob receives the same declared relative destination.
Transitive imports are relative to their immediate importing document's
resulting destination, not always to the original entry destination.
- Filesystem paths still resolve relative to the declaring file, with existing
containment rules. Logical placement and physical file resolution are
independent; there is no root-reset or parent-traversal meaning in destination
key segments. Literal keys follow the same rules as `Input.Destination`.
- Imports retain their existing strict-composition tier. Import order and
destination declarations do not create precedence or permit replacement.
- Existing optionality, repeated-source, cycle, source-kind, and ownership rules
remain intact. A destination does not authorize importing one physical file
twice in a group. Existing source-root directive and reserved-key rules apply.
- `SourceInfo.Destination` reports each import's resulting effective destination.
Effective paths, original locations, resource contexts, protected version
paths, sensitivity, and error behavior retain the placement baseline above.
- Invalid directive shapes fail through the normal structured error boundary;
destination fields must not be silently ignored.
This is declarative access to the existing placement capability. It does not
request recursive glob syntax, implicit directory scanning, namespace inference,
or a new composition strategy.
## Relative Import Collection Entry Example
An entry document selected by the application with destination
`[collections, weatherreporter]` could describe its layout as follows. This
example illustrates relative imports only and assumes no new source checks:
```yaml
configkit:
imports:
- path: metadata.yml
- glob: prompts/*.yml
destination: [prompts]
- glob: profiles/*.yml
destination: [profiles]
- glob: backends/*.yml
destination: [backends]
```
This is proposed syntax, not currently executable configuration. Each required
glob assumes matching files; collections can omit unused categories or use
existing optional-import behavior. The application still selects and provisions
the collection and assigns its namespace. ConfigKit does not discover it.
With current source-version behavior, `metadata.yml` can contain
`version: "2.4.0"` as an ordinary mounted collection release label. Prompt
fragments retain local mapping keys and optional prompt versions, and contribute
beneath the collection's `prompts` mapping. The main application can retain its
own version declaration. Enabling the proposed shared source checks may require
changes to these files once declaration lookup and retention are defined.
Publishers can describe collection layout while applications select collections
and destinations. The later shared-version feature will add opt-in declaration
checking; it does not establish package authenticity or release integrity.
## Deferred And Excluded Work
**Identity-based list composition.** Keep this deferred. Proposal 3's local
mapping keys already identify each installed resource; the two requested
additions do not require a different representation or sequence merge policy.
**Cross-collection inheritance.** Nested collection profiles are not immediate
siblings across collections. Current sibling inheritance remains sufficient for
the initial proposal, with authored definitions flattened where necessary.
Generic cross-path inheritance could be considered separately if consumer needs
justify revisiting the [deferred scope](deferred.md). Mounting must not silently
reinterpret `extends: provider/brief` as a cross-collection reference.
PromptKit also does not request collection installation, automatic directory
discovery, remote fetching, namespace ownership enforcement, dependency solving,
live reload, filename-derived identity, or arbitrary document transformations.
Its qualified resource IDs, optional prompt version semantics, collection
inventory, and execution APIs remain PromptKit responsibilities.
## Public Acceptance Scenarios
Evaluate the implemented placement baseline and accepted relative-import
extension through supported public loading and inspection APIs. Source-version
rows below express the partially accepted direction; their unresolved details
must be settled before they become implementation acceptance tests.
| Scenario | Expected consumer-visible result |
| --- | --- |
| Destination omitted or empty | Existing root-placement behavior |
| Same fragment shape mounted under two distinct collection keys from independent sources | Two separate effective resources |
| Disjoint inputs at the same destination in one group | Mapping contributions combine |
| Equal or unequal overlapping scalar fields in one group | Structured composition conflict with both source locations |
| Same destination across deliberate precedence tiers | Existing recursive overlay behavior |
| Version-only prompt override | Label changes; omitted prompt body fields survive |
| Nested mounted import without a destination directive | Imported fields contribute at the inherited destination |
| Local profile inheritance across mounted peer fragments | Parent resolves at the effective sibling scope |
| Collection metadata containing `version` mounted below root, with new checks disabled | Existing ordinary nested-field behavior |
| Mounted/imported contribution actually targeting the protected version path | Rejected under version ownership rules |
| Mounted field using source-relative content, including inheritance | Original declaring resource context remains available |
| Mounted explicit null and omitted field | Distinct effective presence information |
| Mounted validation error or secret field | Effective paths, physical locations, and existing disclosure rules agree |
| File, native filesystem, byte, reader, or mapping input with equivalent content | Equivalent application values, subject to existing source contracts |
| Unsupported destination or failed load | Normal structured failure, no usable partial result |
| Source-version checks disabled | Existing version-checking and protected-path behavior |
| Check-when-present mode, declaration absent | Omission is allowed |
| Check-when-present mode, declaration present | Must satisfy the shared `Definition.Version` policy |
| Required mode, declaration absent | Structured load failure |
| Required mode, declaration present | Must satisfy the shared `Definition.Version` policy |
| Different declarations both accepted by the consumer checker | No additional exact-equality requirement |
| Rejected source declaration with payload superseded by a higher layer | Rejection still fails the load |
| Relative import destination omitted or empty | Existing inherited-destination behavior |
| Entry at `[collections, weather]`, import at `[prompts]`, nested import at `[extras]` | Nested contribution appears beneath `[collections, weather, prompts, extras]` |
| Glob import with a relative destination | Every matched contribution uses that destination and composes strictly |
| Relative import destination with source-relative resource | Effective configuration path changes; resource base remains the declaring source |
| Load without new source checks or relative import directives | Existing loading and main VersionRule semantics remain unchanged |
The relative-import outcome is a consumer that selects entry sources and
destinations, performs one ordinary layered load, and receives final typed
configuration with reliable diagnostics and inspection. Entry documents describe
their own relative import placements without consumer-side source rewriting,
generic merging, or provenance reconstruction.
The separately scoped, partially accepted source-version extension adds optional
or required declaration checking through the consumer's single
`Definition.Version` policy. It does not require or authorize independently
versioned collections.