CANP-WP-0002 T01: input defaults, static and derived
Closes the gap that made optional inputs unusable: rendering rule 5.1(4) made any unresolved placeholder an error while inputs had no `default`, so an input marked `required: false` and referenced from the template failed every render in which the caller omitted it — including the spec's own section 4 example. Section 10 already carried the derivation mechanism (`requirement: generate`, resolution deliberately undefined), so a derived default needed a binding rather than a new concept: the input's default names a declared prompt dependency. Spec: - 5.1 rewritten as "Resolution and rendering". Resolution may be non-deterministic and must report what it derived; rendering is deterministic and must not derive. A tool that handles only supplied values and static defaults is stated to be conforming. - 6.1 (new) covers both declaration forms. Reference form is preferred, with the reason stated — an inline prompt is anonymous, so unversioned, unprovenanced and un-evaluable — and validators should warn when a published package derives inline. - A derived default may declare a static fallback `value`. Without one the input stays unresolved, which is an error; derivation never silently yields empty content. - 4, 10, 18 (rules 11-13), 19 (two new MUST NOTs), 21 updated accordingly. Reference CLI: - New `resolve` verb reporting the origin of every value. - `Resolution` dataclass and `resolve_inputs`; `resolve_values` kept as a wrapper so existing callers are unaffected. - `render` refuses with a specific error naming underivable inputs rather than substituting empty text. - Tests 3 -> 11. Example package lifecycle re-verified end to end. INTENT.md is unchanged: splitting resolve from render preserves success criterion 4 (deterministic rendering) as written. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjefh8NUiEiahN4JLwoSKM Assistant: claude-code Assistant-Model: opus Assistant-Process: 388925@bnt-lap001 Assistant-Session: 3507023f-e0fd-4a1e-9d90-a0d4217d1502
This commit is contained in:
parent
0caf065544
commit
169db25d25
6 changed files with 600 additions and 36 deletions
|
|
@ -163,6 +163,9 @@ inputs:
|
||||||
type: content
|
type: content
|
||||||
required: false
|
required: false
|
||||||
description: Optional surrounding repository context.
|
description: Optional surrounding repository context.
|
||||||
|
default:
|
||||||
|
derive: context/repository-summary
|
||||||
|
value: "(no repository context provided)"
|
||||||
|
|
||||||
parameters:
|
parameters:
|
||||||
depth:
|
depth:
|
||||||
|
|
@ -186,7 +189,10 @@ compatibility:
|
||||||
providers: []
|
providers: []
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
prompts: []
|
prompts:
|
||||||
|
- id: context/repository-summary
|
||||||
|
version: 1.0.0
|
||||||
|
requirement: generate
|
||||||
context: []
|
context: []
|
||||||
capabilities: []
|
capabilities: []
|
||||||
|
|
||||||
|
|
@ -235,14 +241,44 @@ Review the following change at {{ depth }} depth.
|
||||||
{{ change }}
|
{{ change }}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.1 Rendering rules
|
### 5.1 Resolution and rendering
|
||||||
|
|
||||||
|
Producing a final prompt is two steps:
|
||||||
|
|
||||||
|
1. **Resolve** — determine a value for every declared input and parameter.
|
||||||
|
2. **Render** — substitute those values into the template as text.
|
||||||
|
|
||||||
|
The steps are separate because only the first may be non-deterministic.
|
||||||
|
**Rendering is deterministic:** the same resolved values and the same template
|
||||||
|
always produce the same output. A consumer that satisfies a derived default
|
||||||
|
(§ 6.1) does so during resolution, never during rendering.
|
||||||
|
|
||||||
|
**Resolution rules:**
|
||||||
|
|
||||||
1. Call-supplied values override defaults.
|
1. Call-supplied values override defaults.
|
||||||
2. A declared parameter default is used when no call value is supplied.
|
2. A declared parameter default is used when no call value is supplied.
|
||||||
3. A required input without a value is an error.
|
3. A declared static input default is used when no call value is supplied.
|
||||||
4. A placeholder with no resolved value is an error.
|
4. A required input without a value is an error.
|
||||||
5. Values are substituted as text in v0.1.
|
5. A derived input default is satisfied only by a consumer that is able and
|
||||||
6. Template evaluation MUST NOT execute arbitrary code.
|
permitted to derive it. A consumer that does not derive uses the default's
|
||||||
|
static fallback `value` when one is declared, and otherwise leaves the
|
||||||
|
input unresolved.
|
||||||
|
6. Resolution MUST report which values were derived, so that a caller can see
|
||||||
|
what was added on its behalf before the prompt is used.
|
||||||
|
|
||||||
|
**Rendering rules:**
|
||||||
|
|
||||||
|
7. Values are substituted as text in v0.1.
|
||||||
|
8. A placeholder with no resolved value is an error.
|
||||||
|
9. Template evaluation MUST NOT execute arbitrary code.
|
||||||
|
10. Rendering MUST NOT derive values. A tool offering derivation MUST perform
|
||||||
|
it as a distinct resolve step whose results are visible to the caller
|
||||||
|
before rendering.
|
||||||
|
|
||||||
|
A minimal implementation may implement resolution for supplied values and
|
||||||
|
static defaults only. Such a tool is conforming: it reports an input with an
|
||||||
|
unsatisfied derived default and no static fallback as unresolved, which
|
||||||
|
rule 8 makes an error.
|
||||||
|
|
||||||
CPF v0.1 does not define conditionals, loops, filters, or functions. Implementations MAY offer richer rendering modes only when explicitly declared by an extension; they MUST NOT silently reinterpret a v0.1 template as executable code.
|
CPF v0.1 does not define conditionals, loops, filters, or functions. Implementations MAY offer richer rendering modes only when explicitly declared by an extension; they MUST NOT silently reinterpret a v0.1 template as executable code.
|
||||||
|
|
||||||
|
|
@ -266,6 +302,7 @@ Fields:
|
||||||
| `type` | no | Suggested semantic type; defaults to `content` |
|
| `type` | no | Suggested semantic type; defaults to `content` |
|
||||||
| `required` | no | Whether a caller must supply it; defaults to `false` |
|
| `required` | no | Whether a caller must supply it; defaults to `false` |
|
||||||
| `description` | no | Human-readable explanation |
|
| `description` | no | Human-readable explanation |
|
||||||
|
| `default` | no | Value used when the caller supplies none; see § 6.1 |
|
||||||
|
|
||||||
Recommended v0.1 input types are:
|
Recommended v0.1 input types are:
|
||||||
|
|
||||||
|
|
@ -277,6 +314,94 @@ Recommended v0.1 input types are:
|
||||||
|
|
||||||
These are descriptive hints in v0.1. A runtime MAY use them for validation or adapters.
|
These are descriptive hints in v0.1. A runtime MAY use them for validation or adapters.
|
||||||
|
|
||||||
|
### 6.1 Input defaults
|
||||||
|
|
||||||
|
An input MAY declare a `default`, used when the caller supplies no value.
|
||||||
|
|
||||||
|
`default` MUST NOT be combined with `required: true`: a required input is always
|
||||||
|
supplied by the caller, so a default could never apply.
|
||||||
|
|
||||||
|
Without this field an optional input is close to unusable. Rendering rule 8
|
||||||
|
makes an unresolved placeholder an error, so an input marked `required: false`
|
||||||
|
and referenced from the template would fail every render in which the caller
|
||||||
|
omitted it.
|
||||||
|
|
||||||
|
A default takes one of two forms.
|
||||||
|
|
||||||
|
#### Static default
|
||||||
|
|
||||||
|
A literal value, used as-is:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
inputs:
|
||||||
|
- name: repository_context
|
||||||
|
type: content
|
||||||
|
required: false
|
||||||
|
default: "(no repository context provided)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Every conforming implementation supports static defaults.
|
||||||
|
|
||||||
|
#### Derived default
|
||||||
|
|
||||||
|
A declaration that the value **may** be produced from available context by a
|
||||||
|
consumer able to do so. It is a request to the consumer, not an instruction the
|
||||||
|
package executes.
|
||||||
|
|
||||||
|
The preferred form references a package already declared in
|
||||||
|
`dependencies.prompts` with `requirement: generate` (§ 10):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
prompts:
|
||||||
|
- id: context/repository-summary
|
||||||
|
version: 1.0.0
|
||||||
|
requirement: generate
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
- name: repository_context
|
||||||
|
type: content
|
||||||
|
required: false
|
||||||
|
default:
|
||||||
|
derive: context/repository-summary
|
||||||
|
value: "(no repository context provided)"
|
||||||
|
```
|
||||||
|
|
||||||
|
A derivation prompt MAY instead be written inline:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
inputs:
|
||||||
|
- name: repository_context
|
||||||
|
type: content
|
||||||
|
required: false
|
||||||
|
default:
|
||||||
|
derive:
|
||||||
|
prompt: |
|
||||||
|
Summarize the repository this prompt is being run against,
|
||||||
|
in under 200 words.
|
||||||
|
value: "(no repository context provided)"
|
||||||
|
```
|
||||||
|
|
||||||
|
`derive` is therefore either a string naming a declared prompt dependency, or a
|
||||||
|
mapping carrying an inline `prompt`. A single default MUST NOT use both.
|
||||||
|
|
||||||
|
Prefer the reference form wherever the derivation is worth keeping. An inline
|
||||||
|
prompt is anonymous: it has no version, provenance, examples or evals, and
|
||||||
|
cannot be reused, evaluated or improved independently of its host package —
|
||||||
|
the situation this format exists to replace. Validators SHOULD warn when a
|
||||||
|
**published** package derives inline. Inline derivation is intended for local
|
||||||
|
and draft packages.
|
||||||
|
|
||||||
|
`value` inside a derived default is an optional **static fallback**. Its
|
||||||
|
meaning is defined by resolution rule 5: a consumer that does not derive uses
|
||||||
|
it, and an input with neither a derived value nor a fallback stays unresolved,
|
||||||
|
which rule 8 makes an error. Derivation never silently yields empty content.
|
||||||
|
|
||||||
|
Declaring a derived default does not oblige any consumer to derive anything,
|
||||||
|
and does not make the package depend on a particular resolver, model or
|
||||||
|
runtime. Resolution belongs to the consumer; the package only declares what it
|
||||||
|
would like resolved.
|
||||||
|
|
||||||
## 7. Parameters
|
## 7. Parameters
|
||||||
|
|
||||||
`parameters` is an optional mapping keyed by parameter name.
|
`parameters` is an optional mapping keyed by parameter name.
|
||||||
|
|
@ -377,6 +502,12 @@ Recommended requirement values:
|
||||||
|
|
||||||
This allows richer systems to integrate prompt resolution without forcing simple tools to implement an agent runtime.
|
This allows richer systems to integrate prompt resolution without forcing simple tools to implement an agent runtime.
|
||||||
|
|
||||||
|
A prompt dependency declared `requirement: generate` is the mechanism behind a
|
||||||
|
referenced derived default (§ 6.1): the input's `default.derive` names the
|
||||||
|
dependency, and a consumer able to generate satisfies both at once. Declaring
|
||||||
|
the dependency records *what* may be generated and at which version; the input
|
||||||
|
default records *where the result lands*. Neither states how generation works.
|
||||||
|
|
||||||
## 11. Examples
|
## 11. Examples
|
||||||
|
|
||||||
`examples` is a list of relative paths.
|
`examples` is a list of relative paths.
|
||||||
|
|
@ -492,7 +623,14 @@ A v0.1 validator SHOULD verify at least:
|
||||||
7. referenced example/eval paths do not escape the package;
|
7. referenced example/eval paths do not escape the package;
|
||||||
8. required inputs and parameter names are unique;
|
8. required inputs and parameter names are unique;
|
||||||
9. every template placeholder resolves to a declared input or parameter;
|
9. every template placeholder resolves to a declared input or parameter;
|
||||||
10. no required value is silently omitted during rendering.
|
10. no required value is silently omitted during rendering;
|
||||||
|
11. no input declares both `default` and `required: true`;
|
||||||
|
12. a derived default declares either a `derive` reference or an inline
|
||||||
|
`derive.prompt`, never both;
|
||||||
|
13. a `derive` reference names a package declared in `dependencies.prompts`.
|
||||||
|
|
||||||
|
A validator SHOULD additionally warn when a package intended for publication
|
||||||
|
declares an inline derivation prompt (§ 6.1).
|
||||||
|
|
||||||
## 19. Security requirements
|
## 19. Security requirements
|
||||||
|
|
||||||
|
|
@ -502,6 +640,11 @@ Implementations MUST NOT:
|
||||||
|
|
||||||
- execute code merely because it appears in a package;
|
- execute code merely because it appears in a package;
|
||||||
- treat template expressions as arbitrary code;
|
- treat template expressions as arbitrary code;
|
||||||
|
- treat a derived default's prompt text as instructions addressed to the
|
||||||
|
consuming tool itself; it is content to be resolved on the package's behalf,
|
||||||
|
and it carries no more authority than any other package text;
|
||||||
|
- derive an input default without the caller being able to see that it
|
||||||
|
happened (§ 5.1 rule 6);
|
||||||
- interpolate environment variables or credentials implicitly;
|
- interpolate environment variables or credentials implicitly;
|
||||||
- follow paths outside the package without explicit user action;
|
- follow paths outside the package without explicit user action;
|
||||||
- embed or require secrets in published package metadata.
|
- embed or require secrets in published package metadata.
|
||||||
|
|
@ -566,11 +709,17 @@ Commands:
|
||||||
add PATH validate and copy a package into the local catalog
|
add PATH validate and copy a package into the local catalog
|
||||||
search QUERY search locally installed package metadata
|
search QUERY search locally installed package metadata
|
||||||
show ID display one installed package manifest
|
show ID display one installed package manifest
|
||||||
|
resolve ID report the resolved value of every input and parameter
|
||||||
render ID render an installed prompt with supplied values
|
render ID render an installed prompt with supplied values
|
||||||
publish PATH validate and copy a package into a filesystem registry
|
publish PATH validate and copy a package into a filesystem registry
|
||||||
install ID copy a package version from the registry into the catalog
|
install ID copy a package version from the registry into the catalog
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The reference tool never calls a model, so its `resolve` handles supplied
|
||||||
|
values and static defaults only and reports any input whose derived default it
|
||||||
|
cannot satisfy. `render` performs the same resolution and then substitutes;
|
||||||
|
per § 5.1 rule 10 it never derives.
|
||||||
|
|
||||||
These semantics are illustrative, not mandatory for other implementations.
|
These semantics are illustrative, not mandatory for other implementations.
|
||||||
|
|
||||||
## 22. Worked example
|
## 22. Worked example
|
||||||
|
|
|
||||||
17
README.md
17
README.md
|
|
@ -24,6 +24,10 @@ python canned_prompts.py add ../examples/pqrst-estimate
|
||||||
python canned_prompts.py search pqrst
|
python canned_prompts.py search pqrst
|
||||||
python canned_prompts.py show practice/pqrst-estimate
|
python canned_prompts.py show practice/pqrst-estimate
|
||||||
|
|
||||||
|
# see how each input and parameter resolves, and where the value came from
|
||||||
|
python canned_prompts.py resolve practice/pqrst-estimate \
|
||||||
|
--set session_summary="Implemented feature X, read unfamiliar code, added tests."
|
||||||
|
|
||||||
# render it
|
# render it
|
||||||
python canned_prompts.py render practice/pqrst-estimate \
|
python canned_prompts.py render practice/pqrst-estimate \
|
||||||
--set session_summary="Implemented feature X, read unfamiliar code, added tests."
|
--set session_summary="Implemented feature X, read unfamiliar code, added tests."
|
||||||
|
|
@ -50,6 +54,19 @@ CANNED_PROMPTS_HOME=/some/path
|
||||||
|
|
||||||
or command-level `--catalog` / `--registry` options.
|
or command-level `--catalog` / `--registry` options.
|
||||||
|
|
||||||
|
## Resolution vs rendering
|
||||||
|
|
||||||
|
The format separates the two steps (`CannedPromptFormat-v0.1.md` § 5.1).
|
||||||
|
Resolution decides a value for every input and parameter and may be
|
||||||
|
non-deterministic; rendering substitutes those values and always is. An input
|
||||||
|
may declare a default that is either a static value or a *derived* one — a
|
||||||
|
prompt that a capable consumer may run to produce the value, declared without
|
||||||
|
naming any resolver or model.
|
||||||
|
|
||||||
|
This reference tool never calls a model, so it resolves supplied values and
|
||||||
|
static defaults only, and reports anything it cannot derive instead of
|
||||||
|
rendering a prompt with a silent hole in it.
|
||||||
|
|
||||||
## Deliberate limitations
|
## Deliberate limitations
|
||||||
|
|
||||||
This seed has no hosted registry, model execution, authentication, network access, dependency resolver, or social features. `publish` and `install` operate on a filesystem registry so that the package semantics can be tested before infrastructure is built around them.
|
This seed has no hosted registry, model execution, authentication, network access, dependency resolver, or social features. `publish` and `install` operate on a filesystem registry so that the package semantics can be tested before infrastructure is built around them.
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@
|
||||||
|
|
||||||
This is intentionally a **small reference implementation**, not the intended final architecture.
|
This is intentionally a **small reference implementation**, not the intended final architecture.
|
||||||
|
|
||||||
It demonstrates six verbs:
|
It demonstrates seven verbs:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
add PATH
|
add PATH
|
||||||
search QUERY
|
search QUERY
|
||||||
show ID
|
show ID
|
||||||
|
resolve ID --set key=value
|
||||||
render ID --set key=value
|
render ID --set key=value
|
||||||
install ID [--version VERSION]
|
install ID [--version VERSION]
|
||||||
publish PATH
|
publish PATH
|
||||||
|
|
@ -15,6 +16,11 @@ publish PATH
|
||||||
|
|
||||||
The implementation uses a local catalog plus a filesystem registry and performs no model calls.
|
The implementation uses a local catalog plus a filesystem registry and performs no model calls.
|
||||||
|
|
||||||
|
`resolve` and `render` are separate because the specification separates them
|
||||||
|
(§ 5.1): resolution decides each value and may be non-deterministic, rendering
|
||||||
|
substitutes and always is. `resolve` prints where every value came from —
|
||||||
|
supplied, default, or fallback — before any prompt is produced.
|
||||||
|
|
||||||
## Stores
|
## Stores
|
||||||
|
|
||||||
Default locations:
|
Default locations:
|
||||||
|
|
@ -44,6 +50,10 @@ For example:
|
||||||
- Published versions are immutable by default.
|
- Published versions are immutable by default.
|
||||||
- `install` copies from registry to catalog.
|
- `install` copies from registry to catalog.
|
||||||
- `add` copies a package directly to catalog.
|
- `add` copies a package directly to catalog.
|
||||||
- `search`, `show`, and `render` operate on catalog packages.
|
- `search`, `show`, `resolve`, and `render` operate on catalog packages.
|
||||||
|
- Static input defaults are applied; **derived** defaults (§ 6.1) are not. This
|
||||||
|
tool never calls a model, so a derived default is satisfied only by its
|
||||||
|
static fallback `value`. Without one, `resolve` reports the input as
|
||||||
|
unresolved and `render` refuses rather than substituting empty text.
|
||||||
|
|
||||||
Use this implementation to challenge the format. Replace it once real usage reveals the right architecture.
|
Use this implementation to challenge the format. Replace it once real usage reveals the right architecture.
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Iterable
|
from typing import Any, Iterable
|
||||||
|
|
||||||
|
|
@ -92,6 +93,78 @@ def declared_names(manifest: dict[str, Any]) -> set[str]:
|
||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
def input_default_kind(item: dict[str, Any]) -> str:
|
||||||
|
"""Classify an input's default as 'none', 'static', or 'derived' (§ 6.1)."""
|
||||||
|
if "default" not in item:
|
||||||
|
return "none"
|
||||||
|
default = item["default"]
|
||||||
|
if isinstance(default, dict) and "derive" in default:
|
||||||
|
return "derived"
|
||||||
|
return "static"
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_dependency_ids(manifest: dict[str, Any]) -> set[str]:
|
||||||
|
dependencies = manifest.get("dependencies") or {}
|
||||||
|
if not isinstance(dependencies, dict):
|
||||||
|
raise CannedPromptError("dependencies must be a mapping")
|
||||||
|
prompts = dependencies.get("prompts") or []
|
||||||
|
if not isinstance(prompts, list):
|
||||||
|
raise CannedPromptError("dependencies.prompts must be a list")
|
||||||
|
ids: set[str] = set()
|
||||||
|
for entry in prompts:
|
||||||
|
if isinstance(entry, str):
|
||||||
|
ids.add(entry)
|
||||||
|
elif isinstance(entry, dict) and isinstance(entry.get("id"), str):
|
||||||
|
ids.add(entry["id"])
|
||||||
|
else:
|
||||||
|
raise CannedPromptError(
|
||||||
|
"each prompt dependency must be an id string or a mapping with a string id"
|
||||||
|
)
|
||||||
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
def validate_input_default(item: dict[str, Any], dependency_ids: set[str]) -> None:
|
||||||
|
"""Validation rules 11-13 of § 18."""
|
||||||
|
kind = input_default_kind(item)
|
||||||
|
if kind == "none":
|
||||||
|
return
|
||||||
|
name = item["name"]
|
||||||
|
if item.get("required", False):
|
||||||
|
raise CannedPromptError(
|
||||||
|
f"input {name!r} declares a default and required: true; a required "
|
||||||
|
"input is always supplied, so the default could never apply"
|
||||||
|
)
|
||||||
|
if kind == "static":
|
||||||
|
return
|
||||||
|
|
||||||
|
derive = item["default"]["derive"]
|
||||||
|
if isinstance(derive, str):
|
||||||
|
if not derive.strip():
|
||||||
|
raise CannedPromptError(f"input {name!r}: derive reference is empty")
|
||||||
|
if derive not in dependency_ids:
|
||||||
|
raise CannedPromptError(
|
||||||
|
f"input {name!r} derives from {derive!r}, which is not declared "
|
||||||
|
"in dependencies.prompts"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if isinstance(derive, dict):
|
||||||
|
prompt = derive.get("prompt")
|
||||||
|
if not isinstance(prompt, str) or not prompt.strip():
|
||||||
|
raise CannedPromptError(
|
||||||
|
f"input {name!r}: inline derive requires a non-empty prompt"
|
||||||
|
)
|
||||||
|
extra = sorted(set(derive) - {"prompt"})
|
||||||
|
if extra:
|
||||||
|
raise CannedPromptError(
|
||||||
|
f"input {name!r}: an inline derive cannot also reference a "
|
||||||
|
"dependency; unexpected keys: " + ", ".join(extra)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
raise CannedPromptError(
|
||||||
|
f"input {name!r}: derive must be a dependency id or a mapping with a prompt"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_package(package_dir: Path) -> dict[str, Any]:
|
def validate_package(package_dir: Path) -> dict[str, Any]:
|
||||||
package_dir = package_dir.resolve()
|
package_dir = package_dir.resolve()
|
||||||
if not package_dir.is_dir():
|
if not package_dir.is_dir():
|
||||||
|
|
@ -127,6 +200,11 @@ def validate_package(package_dir: Path) -> dict[str, Any]:
|
||||||
safe_relative_file(package_dir, relative, field)
|
safe_relative_file(package_dir, relative, field)
|
||||||
|
|
||||||
names = declared_names(manifest)
|
names = declared_names(manifest)
|
||||||
|
|
||||||
|
dependency_ids = prompt_dependency_ids(manifest)
|
||||||
|
for item in manifest.get("inputs") or []:
|
||||||
|
validate_input_default(item, dependency_ids)
|
||||||
|
|
||||||
template = template_path.read_text(encoding="utf-8")
|
template = template_path.read_text(encoding="utf-8")
|
||||||
placeholders = set(PLACEHOLDER_RE.findall(template))
|
placeholders = set(PLACEHOLDER_RE.findall(template))
|
||||||
undeclared = sorted(placeholders - names)
|
undeclared = sorted(placeholders - names)
|
||||||
|
|
@ -305,8 +383,23 @@ def supplied_values(pairs: list[str]) -> dict[str, str]:
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
def resolve_values(manifest: dict[str, Any], raw_values: dict[str, str]) -> dict[str, Any]:
|
@dataclass
|
||||||
resolved: dict[str, Any] = {}
|
class Resolution:
|
||||||
|
"""The outcome of § 5.1 resolution, separate from rendering."""
|
||||||
|
|
||||||
|
values: dict[str, Any] = field(default_factory=dict)
|
||||||
|
origins: dict[str, str] = field(default_factory=dict)
|
||||||
|
underivable: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_inputs(manifest: dict[str, Any], raw_values: dict[str, str]) -> Resolution:
|
||||||
|
"""Resolve inputs and parameters per § 5.1.
|
||||||
|
|
||||||
|
This tool never calls a model, so a derived default is satisfied only by its
|
||||||
|
static fallback `value`. An input with a derived default and no fallback is
|
||||||
|
reported as underivable rather than silently rendered as empty.
|
||||||
|
"""
|
||||||
|
result = Resolution()
|
||||||
known: set[str] = set()
|
known: set[str] = set()
|
||||||
|
|
||||||
inputs = manifest.get("inputs") or []
|
inputs = manifest.get("inputs") or []
|
||||||
|
|
@ -314,23 +407,48 @@ def resolve_values(manifest: dict[str, Any], raw_values: dict[str, str]) -> dict
|
||||||
name = item["name"]
|
name = item["name"]
|
||||||
known.add(name)
|
known.add(name)
|
||||||
if name in raw_values:
|
if name in raw_values:
|
||||||
resolved[name] = raw_values[name]
|
result.values[name] = raw_values[name]
|
||||||
elif item.get("required", False):
|
result.origins[name] = "supplied"
|
||||||
|
continue
|
||||||
|
if item.get("required", False):
|
||||||
raise CannedPromptError(f"missing required input: {name}")
|
raise CannedPromptError(f"missing required input: {name}")
|
||||||
|
|
||||||
|
kind = input_default_kind(item)
|
||||||
|
if kind == "static":
|
||||||
|
result.values[name] = item["default"]
|
||||||
|
result.origins[name] = "default"
|
||||||
|
elif kind == "derived":
|
||||||
|
default = item["default"]
|
||||||
|
if "value" in default:
|
||||||
|
result.values[name] = default["value"]
|
||||||
|
result.origins[name] = "fallback (not derived)"
|
||||||
|
else:
|
||||||
|
result.origins[name] = "unresolved (derived, no fallback)"
|
||||||
|
result.underivable.append(name)
|
||||||
|
else:
|
||||||
|
result.origins[name] = "unresolved (optional, no default)"
|
||||||
|
|
||||||
parameters = manifest.get("parameters") or {}
|
parameters = manifest.get("parameters") or {}
|
||||||
for name, spec in parameters.items():
|
for name, spec in parameters.items():
|
||||||
known.add(name)
|
known.add(name)
|
||||||
if name in raw_values:
|
if name in raw_values:
|
||||||
resolved[name] = coerce_value(raw_values[name], spec)
|
result.values[name] = coerce_value(raw_values[name], spec)
|
||||||
|
result.origins[name] = "supplied"
|
||||||
elif "default" in spec:
|
elif "default" in spec:
|
||||||
resolved[name] = spec["default"]
|
result.values[name] = spec["default"]
|
||||||
|
result.origins[name] = "default"
|
||||||
|
else:
|
||||||
|
result.origins[name] = "unresolved (no default)"
|
||||||
|
|
||||||
unknown = sorted(set(raw_values) - known)
|
unknown = sorted(set(raw_values) - known)
|
||||||
if unknown:
|
if unknown:
|
||||||
raise CannedPromptError("unknown values: " + ", ".join(unknown))
|
raise CannedPromptError("unknown values: " + ", ".join(unknown))
|
||||||
|
|
||||||
return resolved
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_values(manifest: dict[str, Any], raw_values: dict[str, str]) -> dict[str, Any]:
|
||||||
|
return resolve_inputs(manifest, raw_values).values
|
||||||
|
|
||||||
|
|
||||||
def render_template(template: str, values: dict[str, Any]) -> str:
|
def render_template(template: str, values: dict[str, Any]) -> str:
|
||||||
|
|
@ -354,11 +472,65 @@ def cmd_render(args: argparse.Namespace) -> None:
|
||||||
manifest = validate_package(package_dir)
|
manifest = validate_package(package_dir)
|
||||||
template_path = safe_relative_file(package_dir, manifest["template"], "template")
|
template_path = safe_relative_file(package_dir, manifest["template"], "template")
|
||||||
raw = supplied_values(args.set_values)
|
raw = supplied_values(args.set_values)
|
||||||
values = resolve_values(manifest, raw)
|
resolution = resolve_inputs(manifest, raw)
|
||||||
rendered = render_template(template_path.read_text(encoding="utf-8"), values)
|
|
||||||
|
template = template_path.read_text(encoding="utf-8")
|
||||||
|
used = set(PLACEHOLDER_RE.findall(template))
|
||||||
|
blocked = sorted(used.intersection(resolution.underivable))
|
||||||
|
if blocked:
|
||||||
|
raise CannedPromptError(
|
||||||
|
"cannot render: this tool does not derive values, and these inputs "
|
||||||
|
"declare a derived default with no static fallback: "
|
||||||
|
+ ", ".join(blocked)
|
||||||
|
+ " — supply them with --set, or add a fallback `value` to the default"
|
||||||
|
)
|
||||||
|
|
||||||
|
rendered = render_template(template, resolution.values)
|
||||||
print(rendered, end="" if rendered.endswith("\n") else "\n")
|
print(rendered, end="" if rendered.endswith("\n") else "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_resolve(args: argparse.Namespace) -> None:
|
||||||
|
catalog = Path(args.catalog).expanduser()
|
||||||
|
package_dir = resolve_installed(catalog, args.id, args.version)
|
||||||
|
manifest = validate_package(package_dir)
|
||||||
|
raw = supplied_values(args.set_values)
|
||||||
|
resolution = resolve_inputs(manifest, raw)
|
||||||
|
|
||||||
|
if args.json:
|
||||||
|
print(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"values": resolution.values,
|
||||||
|
"origins": resolution.origins,
|
||||||
|
"underivable": resolution.underivable,
|
||||||
|
},
|
||||||
|
indent=2,
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
width = max((len(name) for name in resolution.origins), default=0)
|
||||||
|
for name, origin in resolution.origins.items():
|
||||||
|
if name in resolution.values:
|
||||||
|
preview = str(resolution.values[name]).replace("\n", " ")
|
||||||
|
if len(preview) > 48:
|
||||||
|
preview = preview[:45] + "..."
|
||||||
|
print(f"{name:<{width}} {origin:<32} {preview}")
|
||||||
|
else:
|
||||||
|
print(f"{name:<{width}} {origin}")
|
||||||
|
if resolution.underivable:
|
||||||
|
print()
|
||||||
|
plural = len(resolution.underivable) > 1
|
||||||
|
print(
|
||||||
|
"note: "
|
||||||
|
+ ", ".join(resolution.underivable)
|
||||||
|
+ (" declare" if plural else " declares")
|
||||||
|
+ " a derived default this tool cannot satisfy; rendering will fail "
|
||||||
|
+ ("if the template uses them" if plural else "if the template uses it")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def build_parser() -> argparse.ArgumentParser:
|
def build_parser() -> argparse.ArgumentParser:
|
||||||
parser = argparse.ArgumentParser(prog="canned-prompts", description=__doc__)
|
parser = argparse.ArgumentParser(prog="canned-prompts", description=__doc__)
|
||||||
sub = parser.add_subparsers(dest="command", required=True)
|
sub = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
@ -388,6 +560,14 @@ def build_parser() -> argparse.ArgumentParser:
|
||||||
render.add_argument("--set", dest="set_values", action="append", default=[], metavar="NAME=VALUE")
|
render.add_argument("--set", dest="set_values", action="append", default=[], metavar="NAME=VALUE")
|
||||||
render.set_defaults(func=cmd_render)
|
render.set_defaults(func=cmd_render)
|
||||||
|
|
||||||
|
resolve = sub.add_parser("resolve", help="report how each input and parameter resolves")
|
||||||
|
resolve.add_argument("id")
|
||||||
|
resolve.add_argument("--version")
|
||||||
|
resolve.add_argument("--catalog", default=str(default_catalog()))
|
||||||
|
resolve.add_argument("--set", dest="set_values", action="append", default=[], metavar="NAME=VALUE")
|
||||||
|
resolve.add_argument("--json", action="store_true")
|
||||||
|
resolve.set_defaults(func=cmd_resolve)
|
||||||
|
|
||||||
install = sub.add_parser("install", help="install a package from a filesystem registry")
|
install = sub.add_parser("install", help="install a package from a filesystem registry")
|
||||||
install.add_argument("id")
|
install.add_argument("id")
|
||||||
install.add_argument("--version")
|
install.add_argument("--version")
|
||||||
|
|
|
||||||
|
|
@ -51,3 +51,172 @@ def test_undeclared_placeholder_fails(package: Path) -> None:
|
||||||
(package / "prompt.md").write_text("{{ missing }}\n", encoding="utf-8")
|
(package / "prompt.md").write_text("{{ missing }}\n", encoding="utf-8")
|
||||||
with pytest.raises(cp.CannedPromptError, match="undeclared placeholders"):
|
with pytest.raises(cp.CannedPromptError, match="undeclared placeholders"):
|
||||||
cp.validate_package(package)
|
cp.validate_package(package)
|
||||||
|
|
||||||
|
|
||||||
|
def write_pkg(pkg: Path, manifest: str, template: str = "{{ greeting }}\n") -> Path:
|
||||||
|
pkg.mkdir(exist_ok=True)
|
||||||
|
(pkg / "prompt.yaml").write_text(manifest, encoding="utf-8")
|
||||||
|
(pkg / "prompt.md").write_text(template, encoding="utf-8")
|
||||||
|
return pkg
|
||||||
|
|
||||||
|
|
||||||
|
BASE = """\
|
||||||
|
format: canned-prompt/v0.1
|
||||||
|
id: demo/defaults
|
||||||
|
name: Defaults
|
||||||
|
version: 1.0.0
|
||||||
|
summary: Exercise input defaults.
|
||||||
|
template: prompt.md
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_static_default_fills_optional_input(tmp_path: Path) -> None:
|
||||||
|
pkg = write_pkg(
|
||||||
|
tmp_path / "p",
|
||||||
|
BASE
|
||||||
|
+ """\
|
||||||
|
inputs:
|
||||||
|
- name: greeting
|
||||||
|
required: false
|
||||||
|
default: "hello there"
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
manifest = cp.validate_package(pkg)
|
||||||
|
resolution = cp.resolve_inputs(manifest, {})
|
||||||
|
assert resolution.values["greeting"] == "hello there"
|
||||||
|
assert resolution.origins["greeting"] == "default"
|
||||||
|
assert cp.render_template("{{ greeting }}", resolution.values) == "hello there"
|
||||||
|
|
||||||
|
|
||||||
|
def test_supplied_value_overrides_static_default(tmp_path: Path) -> None:
|
||||||
|
pkg = write_pkg(
|
||||||
|
tmp_path / "p",
|
||||||
|
BASE
|
||||||
|
+ """\
|
||||||
|
inputs:
|
||||||
|
- name: greeting
|
||||||
|
required: false
|
||||||
|
default: "hello there"
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
manifest = cp.validate_package(pkg)
|
||||||
|
resolution = cp.resolve_inputs(manifest, {"greeting": "hi"})
|
||||||
|
assert resolution.values["greeting"] == "hi"
|
||||||
|
assert resolution.origins["greeting"] == "supplied"
|
||||||
|
|
||||||
|
|
||||||
|
def test_derived_default_uses_static_fallback(tmp_path: Path) -> None:
|
||||||
|
pkg = write_pkg(
|
||||||
|
tmp_path / "p",
|
||||||
|
BASE
|
||||||
|
+ """\
|
||||||
|
dependencies:
|
||||||
|
prompts:
|
||||||
|
- id: context/greeting
|
||||||
|
version: 1.0.0
|
||||||
|
requirement: generate
|
||||||
|
inputs:
|
||||||
|
- name: greeting
|
||||||
|
required: false
|
||||||
|
default:
|
||||||
|
derive: context/greeting
|
||||||
|
value: "(none)"
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
manifest = cp.validate_package(pkg)
|
||||||
|
resolution = cp.resolve_inputs(manifest, {})
|
||||||
|
assert resolution.values["greeting"] == "(none)"
|
||||||
|
assert resolution.origins["greeting"] == "fallback (not derived)"
|
||||||
|
assert resolution.underivable == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_derived_default_without_fallback_is_underivable(tmp_path: Path) -> None:
|
||||||
|
pkg = write_pkg(
|
||||||
|
tmp_path / "p",
|
||||||
|
BASE
|
||||||
|
+ """\
|
||||||
|
dependencies:
|
||||||
|
prompts:
|
||||||
|
- id: context/greeting
|
||||||
|
version: 1.0.0
|
||||||
|
requirement: generate
|
||||||
|
inputs:
|
||||||
|
- name: greeting
|
||||||
|
required: false
|
||||||
|
default:
|
||||||
|
derive: context/greeting
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
manifest = cp.validate_package(pkg)
|
||||||
|
resolution = cp.resolve_inputs(manifest, {})
|
||||||
|
assert resolution.underivable == ["greeting"]
|
||||||
|
assert "greeting" not in resolution.values
|
||||||
|
with pytest.raises(cp.CannedPromptError, match="unresolved placeholder"):
|
||||||
|
cp.render_template("{{ greeting }}", resolution.values)
|
||||||
|
|
||||||
|
|
||||||
|
def test_inline_derive_is_valid(tmp_path: Path) -> None:
|
||||||
|
pkg = write_pkg(
|
||||||
|
tmp_path / "p",
|
||||||
|
BASE
|
||||||
|
+ """\
|
||||||
|
inputs:
|
||||||
|
- name: greeting
|
||||||
|
required: false
|
||||||
|
default:
|
||||||
|
derive:
|
||||||
|
prompt: Produce a greeting suited to the audience.
|
||||||
|
value: "(none)"
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
manifest = cp.validate_package(pkg)
|
||||||
|
assert cp.resolve_inputs(manifest, {}).values["greeting"] == "(none)"
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_with_required_true_fails(tmp_path: Path) -> None:
|
||||||
|
pkg = write_pkg(
|
||||||
|
tmp_path / "p",
|
||||||
|
BASE
|
||||||
|
+ """\
|
||||||
|
inputs:
|
||||||
|
- name: greeting
|
||||||
|
required: true
|
||||||
|
default: "hello"
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
with pytest.raises(cp.CannedPromptError, match="required: true"):
|
||||||
|
cp.validate_package(pkg)
|
||||||
|
|
||||||
|
|
||||||
|
def test_derive_reference_must_be_declared(tmp_path: Path) -> None:
|
||||||
|
pkg = write_pkg(
|
||||||
|
tmp_path / "p",
|
||||||
|
BASE
|
||||||
|
+ """\
|
||||||
|
inputs:
|
||||||
|
- name: greeting
|
||||||
|
required: false
|
||||||
|
default:
|
||||||
|
derive: context/greeting
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
with pytest.raises(cp.CannedPromptError, match="not declared in dependencies"):
|
||||||
|
cp.validate_package(pkg)
|
||||||
|
|
||||||
|
|
||||||
|
def test_inline_derive_cannot_also_reference(tmp_path: Path) -> None:
|
||||||
|
pkg = write_pkg(
|
||||||
|
tmp_path / "p",
|
||||||
|
BASE
|
||||||
|
+ """\
|
||||||
|
inputs:
|
||||||
|
- name: greeting
|
||||||
|
required: false
|
||||||
|
default:
|
||||||
|
derive:
|
||||||
|
prompt: Produce a greeting.
|
||||||
|
id: context/greeting
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
with pytest.raises(cp.CannedPromptError, match="cannot also reference"):
|
||||||
|
cp.validate_package(pkg)
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ or execution engine that satisfies it.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: CANP-WP-0002-T01
|
id: CANP-WP-0002-T01
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "e49bf036-0aee-5bb2-abbd-3d3f683df298"
|
state_hub_task_id: "e49bf036-0aee-5bb2-abbd-3d3f683df298"
|
||||||
```
|
```
|
||||||
|
|
@ -62,22 +62,56 @@ allow that default to be either
|
||||||
|
|
||||||
Unresolved-with-no-default remains an error.
|
Unresolved-with-no-default remains an error.
|
||||||
|
|
||||||
|
**Design note.** § 10 already carried the derivation mechanism:
|
||||||
|
`dependencies` accepts `requirement: generate`, defined as "a resolver MAY
|
||||||
|
satisfy a missing dependency by invoking an appropriate generation process",
|
||||||
|
with resolution left undefined. A derived default therefore did not need a new
|
||||||
|
concept, only a binding — the input's default names the dependency, and the
|
||||||
|
dependency records what may be generated and at which version.
|
||||||
|
|
||||||
|
**Follow-up decisions (operator, 2026-09-06):**
|
||||||
|
|
||||||
|
- *Both declaration forms are allowed.* A derived default may reference a
|
||||||
|
declared prompt dependency, or carry an inline `prompt`. The reference form
|
||||||
|
is preferred and the spec says why (an inline prompt is anonymous —
|
||||||
|
unversioned, unprovenanced, un-evaluable), and a validator SHOULD warn when a
|
||||||
|
**published** package derives inline. Inline is for local and draft packages.
|
||||||
|
- *Error unless static fallback.* A derived default MAY declare a static
|
||||||
|
`value`. A consumer that does not derive uses it; with no fallback the input
|
||||||
|
stays unresolved, which is an error. Derivation never silently yields empty
|
||||||
|
content.
|
||||||
|
- *Split render from resolve.* Resolution decides values and may be
|
||||||
|
non-deterministic; rendering substitutes and always is. Rendering MUST NOT
|
||||||
|
derive. This preserves INTENT success criterion 4 verbatim — no change to
|
||||||
|
`INTENT.md` was needed.
|
||||||
|
|
||||||
Work:
|
Work:
|
||||||
|
|
||||||
1. Extend § 6 with `default`, and state the two default kinds.
|
Delivered:
|
||||||
2. Specify the derived-default declaration form so it stays inert data: the
|
|
||||||
package declares *what* to derive and the prompt to derive it with; it never
|
1. § 5.1 rewritten as "Resolution and rendering" — six resolution rules and
|
||||||
names or requires a specific resolver, model, or runtime. A consumer that
|
four rendering rules, with rendering explicitly deterministic and forbidden
|
||||||
cannot derive must report the input as unresolved rather than guess.
|
from deriving (rule 10). Resolution must report which values were derived
|
||||||
3. Reconcile § 5.1: rules 1–2 gain input defaults; rule 4 keeps unresolved as
|
(rule 6). A tool that resolves only supplied values and static defaults is
|
||||||
an error; § 5.1's "no conditionals, loops, filters, or functions" and
|
stated to be conforming.
|
||||||
§ 19's "MUST NOT execute code merely because it appears in a package" must
|
2. § 6 gains `default` in the field table plus a new § 6.1 covering both
|
||||||
survive the change — a derived default is a *request to a consumer*, not
|
declaration forms, the static fallback, and why the reference form is
|
||||||
template-embedded execution. Say so explicitly.
|
preferred.
|
||||||
4. Extend § 18 validation accordingly.
|
3. § 4 manifest surface updated: `repository_context` — the field that
|
||||||
5. Implement static defaults in `reference/canned_prompts.py` `resolve_values`
|
demonstrated the original defect — now carries a derived default with a
|
||||||
and add tests. Derived defaults: validate and surface them; the reference
|
fallback, backed by a `requirement: generate` prompt dependency.
|
||||||
CLI never calls a model, so it reports them as unresolved-by-design.
|
4. § 10 states the binding between `requirement: generate` and a referenced
|
||||||
|
derived default.
|
||||||
|
5. § 18 gains validation rules 11–13 and the publish-time inline warning.
|
||||||
|
6. § 19 gains two MUST NOTs: a derived default's prompt text is not an
|
||||||
|
instruction addressed to the consuming tool, and derivation must be visible
|
||||||
|
to the caller.
|
||||||
|
7. § 21 documents the new `resolve` verb.
|
||||||
|
8. `reference/canned_prompts.py`: `Resolution` dataclass, `resolve_inputs`
|
||||||
|
(with `resolve_values` kept as a wrapper), `input_default_kind`,
|
||||||
|
`prompt_dependency_ids`, `validate_input_default`, a `resolve` command, and
|
||||||
|
a specific render-time error naming the underivable inputs. Tests: 3 → 11,
|
||||||
|
all passing. Both READMEs document the split.
|
||||||
|
|
||||||
## Registry namespaces and ownership
|
## Registry namespaces and ownership
|
||||||
|
|
||||||
|
|
@ -113,6 +147,10 @@ capture") and the `dependencies.prompts` field both promise composition, but
|
||||||
v0.1 defines no mechanism — `dependencies` is a declared field with no
|
v0.1 defines no mechanism — `dependencies` is a declared field with no
|
||||||
semantics.
|
semantics.
|
||||||
|
|
||||||
|
T01 has since settled one corner of this: a derived default binds an input to
|
||||||
|
a prompt dependency declared `requirement: generate`, so package-to-package
|
||||||
|
reference already exists for that one case. Build on it rather than around it.
|
||||||
|
|
||||||
Decide what composition means at the *artifact* level: how one package
|
Decide what composition means at the *artifact* level: how one package
|
||||||
references another, whether references are includes, extends, or plain
|
references another, whether references are includes, extends, or plain
|
||||||
declared prerequisites, and how versions are pinned. Leaning: declaration only
|
declared prerequisites, and how versions are pinned. Leaning: declaration only
|
||||||
|
|
@ -154,9 +192,10 @@ manifest surface with no semantics whatsoever in v0.1, and § 9
|
||||||
|
|
||||||
Decide: what a context dependency declares, how it differs from an input, how
|
Decide: what a context dependency declares, how it differs from an input, how
|
||||||
it relates to `compatibility.capabilities`, and whether capability names are
|
it relates to `compatibility.capabilities`, and whether capability names are
|
||||||
free strings in v0.2 (model capability vocabularies stay deferred). Coordinate
|
free strings in v0.2 (model capability vocabularies stay deferred). T01 is now settled and partly answers this: a derived default is a
|
||||||
with T01 — a derived default is a consumer-resolved context requirement, and
|
consumer-resolved context requirement expressed through `dependencies.prompts`
|
||||||
the two mechanisms must not describe the same thing twice.
|
rather than through `dependencies.context`. Decide whether `context` is still a
|
||||||
|
distinct concept or collapses into the prompt-dependency mechanism.
|
||||||
|
|
||||||
## Rewrite specification section 23
|
## Rewrite specification section 23
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue