Collect, reuse and share prompts and prompt templates
Find a file
tegwick ae52931be5 CANP-WP-0006 T01-T02: service skeleton and tenant-keyed schema
The foundation of the hosted registry, in canned-prompts so rapp.yaml gets
ownership_repo: canned-prompts — the sbom-nexus shape, where product ownership
stays out of the operations repo.

Stack matches state-hub and sbom-nexus: FastAPI, SQLAlchemy, Alembic,
PostgreSQL, in service/ with its own environment. reference/ is deliberately
untouched: it is the format's conformance witness and stays dependency-light,
and the service is a separate consumer of the same package semantics.

CANNED_PROMPTS_DATABASE_URL has no default. A service that silently falls back
to a local database when its real one is misconfigured is worse than one that
refuses to start.

Health surface per RailianceAppDeploymentGuide.md: unauthenticated /healthz and
/readyz, plus /state/health for fleet consistency. /healthz deliberately checks
nothing beyond the process being up, so a database blip does not restart pods;
/readyz asks the database something it can fail to answer.

Migration 0001 creates package_versions, package_files and index_entries, every
one carrying a tenant key per business-app-service-contract section 1.3 — the
service is single-tenant today, and the key is present so a later consolidation
is a data copy rather than a rewrite. A test asserts every table in the metadata
is tenant-keyed, so adding an unkeyed table fails the suite rather than being
discovered at consolidation time. Uniqueness is (tenant, registry, package_id,
version): registry-scoped because identity is, tenant-scoped so two tenants may
hold the same id.

The schema keeps the format's three things distinct — an immutable package
version, its files as content rather than parsed rows, and an index entry
recording how a version arrived here.

Fixes a bug its own test caught: check_readiness first caught every failure in
one except and reported "database unreachable", so an unmigrated but perfectly
reachable database sent an operator to credentials and networking when the fix
was alembic upgrade. Connectivity and schema are now checked separately.

Service tests 11 passing; reference tests unaffected at 99.

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
2026-09-06 19:57:47 +02:00
examples Package the canonical PQRST prompt faithfully 2026-09-06 15:26:36 +02:00
reference CANP-WP-0003: name the default registry default 2026-09-06 19:32:30 +02:00
service CANP-WP-0006 T01-T02: service skeleton and tenant-keyed schema 2026-09-06 19:57:47 +02:00
workplans CANP-WP-0006 T01-T02: service skeleton and tenant-keyed schema 2026-09-06 19:57:47 +02:00
.custodian-brief.md chore(consistency): sync task status from DB [auto] 2026-09-06 00:47:38 +02:00
.gitignore CANP-WP-0006 T01-T02: service skeleton and tenant-keyed schema 2026-09-06 19:57:47 +02:00
.repo-classification.yaml Add repo classification and record workplan review context 2026-09-06 00:47:23 +02:00
AGENTS.md Register with Custodian State Hub and seed format open-questions workplan 2026-09-06 00:45:28 +02:00
CannedPromptFormat.md CANP-WP-0005: document and detect inclusion diamonds 2026-09-06 19:23:16 +02:00
INTENT.md Register with Custodian State Hub and seed format open-questions workplan 2026-09-06 00:45:28 +02:00
README.md CANP-WP-0003: name the default registry default 2026-09-06 19:32:30 +02:00
SCOPE.md CANP-WP-0002 T06: revision v0.2, and section 23 rewritten 2026-09-06 14:22:45 +02:00
WORK-RECORDS.md chore(consistency): register CANP-WP-0006 and regenerate WORK-RECORDS.md 2026-09-06 19:49:18 +02:00

canned-prompts seed

Collect, reuse and share prompts and prompt templates.

This bundle contains a first project seed for canned-prompts:

Try the reference implementation

cd reference
python -m venv .venv
. .venv/bin/activate            # Windows: .venv\Scripts\activate
pip install -r requirements.txt

# add the included examples to your local catalog
python canned_prompts.py add ../examples/house-style
python canned_prompts.py add ../examples/pqrst-estimate

# find and inspect it
python canned_prompts.py search pqrst
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
python canned_prompts.py render practice/pqrst-estimate \
  --set session_summary="Implemented feature X, read unfamiliar code, added tests."

# publish it to the local filesystem registry
python canned_prompts.py publish ../examples/pqrst-estimate

# remove the local catalog if you want to simulate another machine, then install
python canned_prompts.py install practice/pqrst-estimate --version 0.1.0

Packages added from a path are filed under the registry name local; packages installed from a registry are filed under that registry's name. search prints qualified references:

house:practice/pqrst-estimate@0.1.0  PQRST Estimate
local:practice/pqrst-estimate@0.1.0  PQRST Estimate

By default the reference tool uses:

~/.canned-prompts/catalog
~/.canned-prompts/default    # the default registry

Override them with:

CANNED_PROMPTS_HOME=/some/path

or command-level --catalog / --registry options.

Resolution vs rendering

The format separates the two steps (CannedPromptFormat.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.

Composition

A package composes another by declaring it as a dependency and binding it to an input default. There are two kinds:

  • include — inline the other package's rendered template as text. Deterministic, needs no model, and the reference CLI performs it.
  • derive — use the other package's result, obtained by running it. Only a consumer able to run it can supply one.

examples/pqrst-estimate includes examples/house-style, so a shared style block is a versioned package rather than copied text:

dependencies:
  prompts:
    - id: practice/house-style
      version: ">= 0.1.0"
      requirement: required

inputs:
  - name: house_style
    required: false
    default:
      include: practice/house-style

A dependency pins an exact version by default; any, newest and a >= X.Y.Z lower bound are explicit opt-ins. These are per-dependency selectors, not version ranges — there is no solver, and constraint resolution across a dependency graph remains a non-goal.

There is no template inheritance. A package never extends another or overrides its parts; composition is by reference only, so a package's content stays readable without chasing ancestors.

Evals

An eval file declares what to assess. evals/ used to hold whatever an author put there; it now has one schema the tooling understands, split along the same line as composition:

  • render checks — deterministic assertions about the rendered prompt (contains, not_contains, resolves_all). No model needed, so the reference CLI runs them.
  • output criteria — statements about a good result. Declared, not run.
python canned_prompts.py eval practice/pqrst-estimate
local:practice/pqrst-estimate@0.2.0
  evals/quality.yaml (pqrst-estimate-quality)
    render  PASS  contains "must sum to exactly 100%"
    render  PASS  resolves_all
    output  --    4 criteria declared (not run: judging output needs a model)

An eval declares assessment, never results. Results are run evidence and live outside the immutable package.

Required versus compatible

Two fields used to say overlapping things about capabilities. They now differ in one word each:

Field Meaning Absence means
dependencies required — does not work without it a consumer should warn or refuse
compatibility observed — known to work with it nothing; it is information

Dependencies come in three kinds, separated by what the format can do about them: prompts are packages it resolves by id and version, context names things it does not package at all (an information space, an API, a corpus the caller supplies), and capabilities are what the environment must be able to do. resolve lists the last two, since the reference tool cannot verify either:

requires (this tool cannot verify these):
  capability  web-search
  context     repository-tree — A listing of the repository under review.

Registries and identity

An id names a package within a registry (CannedPromptFormat.md § 3.2). The same id obtained from two registries may be two different packages, so the catalog keeps them apart and a bare id that matches more than one is reported as ambiguous rather than guessed. Qualify it when you need to:

python canned_prompts.py render house:practice/pqrst-estimate --set ...

A registry may describe itself with an optional registry.yaml naming it and recording which namespaces are claimed and under what policy. Those claims are descriptive: a filesystem registry cannot authenticate a publisher, and signing and trust scoring are explicit non-goals. Ownership lives with the registry rather than in the package, so no package carries an unverifiable assertion of authority.

The index

Every store keeps an index.yaml recording which package versions entered it, from where, and when:

python canned_prompts.py index
local:helix/repo-orient@0.1.0  2026-09-06T13:31:02Z  add
                               from /home/worsch/helix-forge/prompts/repo-orient
                               declares source personal prompt collection, contributed 2026-09-06

This is store metadata, not package data. provenance records who wrote a prompt; the index records how a copy arrived here — which differs for every consumer, and which must never rewrite the package it describes. included_at is first arrival and is never overwritten; a re-run updates last_seen_at.

Dependencies are reported, not fetched

Installing a package that composes another warns when the dependency is absent, including when it is present but no version matches:

declared dependencies not in this catalog: practice/house-style@>= 0.1.0
  — install them, or composition referencing them will not resolve

Nothing is fetched automatically. Dependency resolution is left to the consumer, but a package that installs cleanly and then cannot render is worse than one that says what it is waiting for.

Packaging and versions

add, publish and install copy the reserved paths (prompt.yaml, prompt.md, README.md, LICENSE, examples/, evals/, assets/) plus anything a manifest field references — and nothing else, as § 2 requires. What was left behind is reported rather than silently dropped:

not packaged (not a reserved path, not referenced by the manifest): .git/, .venv/, notes.txt

Version precedence follows SemVer, so 1.0.0 outranks 1.0.0-rc1, and any, newest and >= X.Y.Z skip prereleases entirely. Publishing a release candidate never changes what existing consumers resolve to; name it exactly to use it.

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.