Files
promptkit/docs/roadmap/namespaces.md

456 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Namespaces And Scoped Resource Lookup
Status: feature roadmap; not implemented. The reserved-slash naming convention,
reference-resolution rules, and decisions 1–3 (scope shape, validation, and
integrated discovery filtering) are accepted target behavior. Decision 4,
catalog migration, remains open. Exact Go names and option types remain delivery
details within the accepted semantics.
## Purpose
Let consumers select a namespace once and use convenient local names within
that namespace across PromptKit operations. An assembled engine may contain
multiple application prompt libraries and provider profile catalogs with
overlapping local names. Resolution must remain unambiguous.
The motivating cases are:
- WeatherReporter and another application both define `summarize.alerts`.
Each consumer selects its application namespace and uses the local name.
- OpenRouter and RakestrawHome both provide the model identified in the
discussion as `google/gemma-4-31b-it`. Consumers distinguish the execution
profiles by provider namespace while retaining convenient local naming.
Model names here illustrate identity and resolution, not provider availability
or a promise of maintained catalog entries.
The primary need is organization and convenient lookup within assembled
libraries. Ansible collections are a conceptual reference for qualified names;
package distribution and relocatable collection imports are not required for
this feature.
## Existing Foundation
The [format reference](../formats.md) defines prompt, profile, and backend keys
as exact literal IDs. Punctuation is already allowed, and references are not
trimmed or re-keyed. Merely adopting prefixed IDs therefore does not provide
the requested scoped lookup behavior.
Existing prompt and profile listings expose assembled identities. The
[consumer guide](../consumers/pkg-promptkit.md) describes current operations;
their Go declarations and GoDoc remain authoritative until this feature lands.
[ADR 0001](../adr/0001-configuration-boundary.md) assigns source loading,
composition, and inheritance to ConfigKit. This proposal adds interpretation
of consumer-supplied resource names against the final effective configuration,
without a second source-composition pass.
## Accepted Identity Convention
Reserve `/` exclusively as the namespace separator in PromptKit prompt, profile,
and backend identifiers and references. A qualified identifier has exactly two
nonempty components: `namespace/local-name`. Neither component can contain `/`.
Dots remain available within local names. An unqualified identifier has no slash
and remains valid under the existing nonblank-ID rule.
Reject resource identifiers or supplied references with multiple slashes or an
empty qualified component, such as `a/b/c`, `/name`, and `namespace/`. These
rules do not apply to provider model values, endpoints, resource paths, or prompt
versions. Preserve exact spelling; do not silently trim or rewrite IDs.
| Resource | Namespace | Local name | Qualified ID |
| --- | --- | --- | --- |
| Prompt | `weatherreporter` | `summarize.alerts` | `weatherreporter/summarize.alerts` |
| Prompt | `homewatch` | `summarize.alerts` | `homewatch/summarize.alerts` |
| Profile | `openrouter` | `google.gemma-4-31b-it` | `openrouter/google.gemma-4-31b-it` |
| Profile | `rakestrawhome` | `google.gemma-4-31b-it` | `rakestrawhome/google.gemma-4-31b-it` |
Prompt, profile, and backend identities remain separate resource types. The
same namespace can contain all three, and resources in it may come from
multiple configuration sources. A namespace does not imply a directory,
package, provider model identifier, or security boundary.
A profile ID identifies execution settings. Its configured model value is
still the identifier passed to the provider; qualifying the profile ID does
not modify that value. Both example profiles may retain the configured model
`google/gemma-4-31b-it`. Different profiles may select the same model with
different settings. Prompt versions also remain separate from namespaces and
continue to use the existing selection rules.
Retain unqualified and dotted IDs. Existing slash-containing IDs need review:
those with one slash and nonempty components become qualified identities, while
those outside the new grammar require migration. This intentional format break
is accepted in favor of unambiguous resolution; the migration work is described
below.
## Accepted Consumer Scope
Provide a lightweight, immutable view over an existing engine with independent
prompt, profile, and backend scopes. The following sketch illustrates the
accepted shape; method/type names and final signatures remain provisional:
```go
weather, err := engine.WithNamespaces(Namespaces{
Prompt: "weatherreporter",
Profile: "openrouter",
})
// Handle err, then use local prompt and profile names through weather.
```
This sketch follows [decision 1](#1-public-scope-shape-accepted); these
declarations do not exist yet.
The view would share the engine's captured configuration, clients, and execution
machinery. Selecting a namespace would not rebuild the engine or duplicate its
backend capacity accounting. Consumers could use different views concurrently
without changing a shared current-namespace setting.
The accepted resolution rules, independent of the final scope API, are:
1. A supplied name without a slash is qualified using the selected scope for its
resource type, then looked up exactly. Without that scope, it is looked up as
an exact unqualified ID.
2. A missing scoped resource does not trigger a search in other namespaces or
among unqualified IDs. Loading another collection cannot redirect lookup.
3. A valid supplied name containing one slash is fully qualified and is looked
up exactly, regardless of scope. Malformed resource references are rejected.
4. Scope applies to caller-supplied resource names. Stored references, including
prompt default profiles, profile backends, inheritance references, and backend
smoke-test default profiles, retain their existing exact meaning. They are
not rebound to the invoking consumer's namespace.
5. Omitted optional IDs retain their existing selection/default behavior; an
omission is not converted into a namespace prefix or a lookup for an empty
local name.
6. Where outputs, inspection, errors, or diagnostic reports identify a selected
resource, preserve its resolved full identity. Any exposed local name should
be distinguishable from that identity.
Keep qualification consistent across execution, preparation, request validation,
prompt/profile inspection, and applicable smoke-test selections. Namespace
selection must not change request override precedence, prompt version selection,
or provider model strings.
### Local And Qualified References In One Operation
Syntax alone distinguishes local and qualified references. Do not decide whether
to qualify a name by checking which candidates exist; that would make resolution
depend on loaded content.
Within a `weatherreporter` prompt scope, `summarize.alerts` resolves only to
`weatherreporter/summarize.alerts`, while `homewatch/summarize.alerts` explicitly
selects that other library's prompt. A qualified profile override similarly
bypasses the profile scope. Scoped operations accept both forms without a
separate reference type or requiring a return to the original engine for
cross-namespace access.
The original engine remains the way to select an exact unqualified ID without
applying a scope. No escape syntax for unqualified IDs is introduced here.
### Separate Resource Scopes
A consumer can select prompt shorthand under `weatherreporter` and profile
shorthand under `openrouter` in the same request. Each resource type has an
independent scope, as defined in [decision 1](#1-public-scope-shape-accepted).
A prompt's configured default profile needs no caller profile scope because
the stored reference is already exact. Scope syntax and existence handling are
defined in [decision 2](#2-scope-validation-accepted).
## Discovery And Collection Organization
Namespace enumeration and filtering are integrated into PromptKit's listing
APIs. Consumers select a filter and receive matching full resource identities;
they do not need to fetch all resources and filter them themselves. Defaults,
explicit filter overrides, and result semantics are defined in
[decision 3](#3-discovery-accepted).
Derive namespace membership from the qualified identifier's namespace component:
`weather` must not include `weatherreporter/...`. Nested slash-separated
namespaces are outside the accepted grammar.
The initial proposal does not require a collection manifest. Titles,
descriptions, and other collection metadata can be considered if consumers
need more than identity and grouping.
## Composition And Compatibility
Qualified identities reduce accidental collisions between independently
authored libraries. Definitions with the same exact qualified identity still
follow the existing ConfigKit overlay and inheritance rules; namespaces do not
create a separate collision policy.
Leave source composition and configuration inspection with ConfigKit. Do not
introduce implicit qualification of definition keys, mounting under caller-chosen
prefixes, or rewriting of stored references as part of consumer scope. Those
would be separate configuration-design work.
Existing unscoped exact lookup remains available for IDs valid under the new
grammar. Compatibility with arbitrary slash-containing resource IDs is
intentionally broken. The migration must:
- Audit maintained catalogs and representative consumer definitions for invalid
IDs and for valid one-slash IDs whose namespace meaning needs review.
- Choose explicit old-to-new mappings and check for collisions. Do not
automatically replace slashes with dots: distinct existing IDs could collapse
to the same new identity.
- Update renamed keys and their references together, including inheritance,
prompt default profiles, profile backend references, backend smoke-test
defaults, and affected examples and consumer request IDs.
- Keep provider model strings unchanged. Renaming a profile does not rename
the upstream model.
- Document consumer action and coordinate the PromptKit format change with
catalog adoption under the [release policy](../policy/release.md). Its catalog
profile-data exception does not cover this general format/API behavior change;
account for its explicitly retained built-in profile names when selecting
migrations.
Renaming a backend changes its identity; duplicating it under another ID can
separate capacity accounting. Do not use blanket aliases or duplicate catalog
entries as an automatic compatibility mechanism. Exact release versions and
catalog mappings remain implementation-planning work.
The [architecture policy](../policy/architecture.md) continues to assign CLI
syntax, flags, and application policy to consumers such as Scriptorium. PromptKit
would supply reusable scope and resolution semantics.
## Work Not Included In The Initial Proposal
- Package installation, collection registries, and dependency resolution.
- Caller-selected collection mount points or import alias rewriting.
- Implicit namespace search paths or fallback to whichever resource exists.
- Namespace-based access control or filesystem isolation.
- Changes to model names, inference behavior, or configuration precedence.
These boundaries limit the initial proposal; they do not prohibit future work
if a concrete consumer need emerges.
## Delivery Outline
1. Resolve decision 4 and audit existing identifier usage against the accepted
grammar. Record explicit mappings and coordinated migration work. Finalize
Go names and option types that express the accepted decisions 1–3.
2. Implement the smallest public scope surface with shared name qualification
and existing engine collaborators. Cover supported operations consistently.
3. Add integrated namespace filtering and representative consumer examples.
4. Verify compatibility and scoped resolution through focused offline tests,
following the [testing policy](../policy/testing.md). Update GoDoc, the
format reference, consumer guide, and relevant internal documentation as
behavior lands; retire this roadmap when its work is complete.
Acceptance cases for the settled rules; migration-specific cases follow the
eventual decision 4:
- Two prompt scopes resolve the same local name to their respective prompts;
a missing name never resolves from the other scope or an unqualified entry.
- Two provider profile scopes resolve the same local name, such as
`google.gemma-4-31b-it`, to different profiles without changing the configured
provider model value `google/gemma-4-31b-it`.
- A qualified reference bypasses the selected scope without double qualification;
adding another collection does not change local or qualified resolution.
- Multiple slashes and empty qualified components are rejected in resource IDs
and references, while provider model values retain their existing syntax.
- Prompt shorthand and a profile override use their intended scopes, while
stored default references and omitted selections retain their meaning.
- Scoped views coexist concurrently and share backend capacity with ordinary
calls on the underlying engine.
- Scope creation rejects invalid syntax, accepts well-formed absent namespaces,
and treats empty scope fields as no scope. Missing resources fail at lookup;
qualified references remain usable even when the default namespace is absent.
- Listing without an explicit filter uses the resource's view scope, or lists
all resources when no scope is selected. Explicit named, unqualified-only,
and all-namespaces filters override that default for the current listing only.
- A valid namespace with no matches produces an empty listing. Namespace
filtering respects resource type and exact namespace boundaries.
- Existing exact-ID calls using valid IDs, intentional overlays, and prompt
versions retain their behavior. Migrated catalog references resolve to their
intended resources without collisions.
- Listings preserve full IDs and separate prompt versions. Qualified results
round-trip through either engine or view; unqualified results round-trip
through the engine without a corresponding scope.
- Representative validation, inspection, execution, and smoke-test paths use
the same qualification rules without duplicating every test at every layer.
## Decisions And Remaining Recommendation
Decisions 1–3 are accepted; their alternatives are retained as considered but
not selected. Decision 4 remains open, with its recommendation and alternative
unchanged for further discussion.
### 1. Public Scope Shape (Accepted)
**Accepted: an immutable view with independent resource scopes.** Provide one
engine operation that accepts a small value with prompt, profile, and backend
namespace fields and returns a scoped view or an error. The illustrative
`WithNamespaces` and `Namespaces` names above are provisional. Empty fields mean
no scope for that resource type. The view captures the value and delegates to
the same engine; it does not expose a mutable namespace setting. Apply the
prompt field to caller-supplied prompt IDs, the profile field to profile IDs,
and the backend field to backend IDs wherever those selections are supported.
Qualified references bypass the corresponding field.
This directly serves the select-once workflow and the common mixed case of an
application prompt namespace with a provider profile namespace. It also keeps
scope out of stored configuration and makes concurrent use independent of call
order. The cost is an additional facade surface whose supported operations must
remain consistent with the engine. Keep qualification shared rather than
duplicating selection logic in each wrapper.
Initially omit an ambiguous one-string `InNamespace` shorthand. Repeating the
same namespace in several fields happens once when creating a view and states
exactly which resource types it affects. A convenience operation can follow if
that repetition is a demonstrated burden.
**Considered alternative, not selected: one namespace per immutable view,
applied to all resource types.** A one-string operation would scope every
unqualified prompt, profile, and backend selection; callers would use qualified
references for resources in other namespaces. This is smaller and reads naturally for self-contained
application collections. It is less convenient when prompts and profiles
habitually come from different namespaces, because profile overrides would
repeatedly need qualification. Prefer it if consumers primarily use prompts'
configured defaults and rarely select profiles themselves.
**Considered alternative, not selected: explicit scope values supplied per
operation.** Add scope to request values and appropriate inspection/listing
options instead of returning a view. This makes every invocation self-contained
and suits applications that
choose a different scope for every request. It spreads scope through more call
sites and API shapes, and consumers would need their own reusable wrapper to
achieve the requested select-once experience. The immutable view is therefore
the better default for the use case discussed so far.
### 2. Scope Validation (Accepted)
**Accepted: validate syntax when creating the view; resolve existence when
an operation selects a resource.** In the scope value, the empty
string means no scope. Reject a nonempty namespace containing `/` or consisting
only of whitespace. Otherwise preserve its exact spelling, with no trimming,
case folding, or additional character whitelist. This distinguishes omission
from an unusable namespace without inventing normalization rules. Report invalid
scope construction through PromptKit's existing public error conventions.
Allow a well-formed namespace even when it has no current members. Its scoped
listing would be empty, and selection of a missing local resource would produce
the ordinary lookup error. Qualified selections would remain usable regardless
of the view's default scope. An empty optional request ID continues to mean
omission under that operation's existing contract; it is never qualified.
This keeps scope as a naming context and supports uniform application setup
across installations with different library contents. It also avoids treating
successful scope construction as validation of a prompt or execution profile.
The tradeoff is that a misspelled but syntactically valid namespace is detected
only when listed or used, rather than during view construction.
**Considered alternative, not selected: require each selected namespace to have
resources of its corresponding type when creating the view.** For an immutable
assembled engine, this can catch spelling mistakes immediately and gives startup checks useful
feedback without attempting execution. However, it rejects empty optional
collections and a view intended to use qualified references outside its default
namespace. A namespace having one member also does not establish that the
particular required prompt/profile exists. Prefer this approach only if eager
inventory validation is itself an intended consumer contract; it should not be
an accidental consequence of implementing scope creation.
### 3. Discovery (Accepted)
**Accepted: integrated namespace filtering and full-identity results.**
Enumerate the distinct named namespaces present in final effective
prompts, profiles, and backends. Allow filtering that enumeration by resource
type so a prompt picker need not show profile-only namespaces. Provide scoped
listings for the supported resource types, adding a backend inventory surface
as part of this work if needed. Namespace filtering is an explicit option in
the library listing API, not a consumer-side filtering helper. Preserve existing
unfiltered engine calls and their result contracts; the exact Go option shape
remains to be specified.
| Selection | Listing behavior |
| --- | --- |
| No explicit filter on the engine | All namespaces, including unqualified resources |
| No explicit filter on a scoped view | The selected scope for that resource type; all resources of that type if no scope is selected |
| Explicit named namespace | Resources in exactly that namespace |
| Explicit unqualified-only filter | Resources whose IDs have no namespace |
| Explicit all-namespaces filter | All resources of that type, including unqualified resources |
An explicit filter replaces the view's default for the current listing; it
does not intersect with that default or mutate the view. A named namespace
filter uses the namespace syntax rules in decision 2. A well-formed namespace
with no matching resources returns an empty listing. Represent default, named,
unqualified-only, and all-namespaces selections distinctly in the API; do not
overload an empty string to mean several of them.
Return full IDs, retaining prompt versions as separate identity fields. Do not
invent a named namespace such as `default` for unqualified resources. Namespace
enumeration excludes that unnamed group. Use deterministic ordering consistent
with existing listing contracts.
Qualified result IDs can be passed unchanged to either scoped or unscoped
operations. Unqualified IDs remain literal unqualified identities: when an
explicit all-namespaces or unqualified-only filter exposes one through a scoped
view, use the original engine or a view without that resource scope to select
it exactly. A listing filter does not change subsequent execution scope. This
preserves the accepted lookup rules without introducing an escape syntax.
Integrated filtering gives consumers a direct way to discover the desired
resources, while full identities keep logs and subsequent selections
unambiguous. Namespace/local-name display decomposition is separate from
filtering; a public splitting helper is not required by this decision.
**Considered alternative, not selected: add a richer inventory API returning
full ID, namespace, local name, resource type, and prompt version where
applicable.** Preserve existing list methods and expose the structured form
separately. This is attractive for
Scriptorium-style library browsers: grouping and display require no parsing,
and one inventory operation can cover all resource kinds. It creates another
public representation and overlaps existing listing APIs, so its value should
justify that maintenance. Prefer it if downstream clients routinely need a
combined catalog view rather than a resource picker or namespace filter. In
either approach, the full ID remains the authoritative selection value.
### 4. Catalog Migration (Open)
**Recommended: coordinate the grammar change with adoption of provider
namespaces for ordinary maintained catalog profiles.** Use provider namespaces
such as `openrouter` and `rakestrawhome`, with descriptive slash-free local
profile names. Use `google.gemma-4-31b-it` for the illustrative model-based
profile name; profiles with distinct execution settings need distinct local
names even when they select the same model. Assign every renamed profile an
explicit old-to-new mapping after auditing the catalog, including valid old IDs
that currently encode a provider prefix differently. Do not infer mappings by
blind punctuation replacement.
Retain the two unqualified built-in profile names explicitly guaranteed by the
release policy. Keep existing valid backend IDs unless there is a concrete
collision or organizational reason to rename them; provider-qualified profiles
can reference those exact backend IDs without duplicating backend identity or
capacity accounting. Application-owned prompt libraries choose their own
namespace and migration mappings rather than being silently rewritten by
PromptKit.
Prepare catalog data, affected references, consumer migration instructions, and
the PromptKit grammar change as coordinated work. Select compatible catalog
revisions for the PromptKit release and follow each repository's release rules.
Choose a PromptKit version permitted to break the format contract: a minor
release before v1, or a major release at/after v1, under the current release
policy. Exact versions and actual old-to-new mappings require the catalog audit;
this recommendation does not claim those mappings have already been established.
This makes the initial feature useful with maintained provider catalogs and
concentrates consumer migration into one planned transition. Retaining the
explicitly supported unqualified profile names preserves their selected
contract. The cost is a broader coordinated change than grammar enforcement
alone, including renames of otherwise valid ordinary profile IDs.
**Alternative: migrate only IDs that violate the new grammar, and defer broader
catalog naming changes.** Keep all other exact IDs, including valid one-slash
IDs whose first component would now be their namespace. Ship scope support and
let new libraries adopt provider/application namespaces incrementally. This
reduces immediate consumer changes and makes the format update easier to review
and release independently. It leaves existing catalogs with inconsistent
grouping, may expose model-vendor rather than provider namespaces, and can
require another migration to realize the intended provider shorthand. Prefer
it if the audit reveals that broad catalog renaming would impose substantial
downstream disruption relative to current demand.
The delimiter, two-component qualified grammar, syntax-based resolution, and
decisions 1–3 are settled. The need for a breaking migration is accepted, but
decision 4's migration strategy, exact mappings, and release coordination remain
open for further discussion.