ADR-CF02: Prompt Catalog Discovery Strategy¶
Extends the ADR-CF01 three-layer configuration model to prompt catalog discovery, providing consistent runtime resolution for user prompts (production) and dev prompts (workspace development).
- Status: Accepted
- Date: 2026-02-05
- Authors: Claude Opus 4.5
- Owner: aaronksolomon
Context¶
Current State¶
Prompt directory resolution is fragmented across multiple mechanisms:
| Source | Default Value | Mechanism |
|---|---|---|
__init__.py:59 |
TNH_PROJECT_ROOT_DIR / "prompts" |
Hard-coded module constant |
GenAISettings.prompt_dir |
Falls back to TNH_DEFAULT_PROMPT_DIR |
Env vars PROMPT_DIR or TNH_PROMPT_DIR |
PromptSystemSettings.tnh_prompt_dir |
Path("prompts/") (relative) |
Env var TNH_PROMPT_DIR |
tnh-gen config_loader |
Gets from GenAISettings |
5-level precedence chain |
Problems¶
-
Hard-coded repo path:
TNH_DEFAULT_PROMPT_DIRin__init__.pypoints to<repo>/prompts/, which fails when tnh-scholar is installed as a package (no repo structure). -
Relative path ambiguity:
PromptSystemSettingsdefaults toPath("prompts/")- behavior depends on current working directory. -
No user vs dev distinction: No clean separation between:
- User prompts: Personal, persistent, lives in
~/.config/tnh-scholar/prompts/ -
Dev prompts: Workspace-local, cloned from
tnh-promptsrepo for active development -
Inconsistent with registry pattern: TNHContext implements three-layer discovery for registries (
RegistryPathBuilder), but prompts don't use this established pattern. -
Import-time side effects:
__init__.pyraisesFileNotFoundErrorat import if expected paths don't exist, breaking package installs.
Constraints¶
- Prompts are not tracked in tnh-scholar's git repo. They live in a separate
tnh-promptsrepository. - End users install prompts via
tnh-setup, which clones to~/.config/tnh-scholar/prompts/. - Developers clone
tnh-promptsinto their workspace'sprompts/directory for active development. - A minimal fallback set should ship with the package for basic functionality.
Decision¶
1. Extend TNHContext with Prompt Path Discovery¶
Add PromptPathBuilder to src/tnh_scholar/configuration/context.py, following the same pattern as RegistryPathBuilder:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PROMPT DIRECTORY PRECEDENCE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β 1. Workspace prompts β <workspace>/prompts/ β
β (dev use) β Discovered via TNHContext.workspace_root β
β β Clone of tnh-prompts repo for development β
βββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββ€
β 2. User prompts β ~/.config/tnh-scholar/prompts/ β
β (production) β Installed via tnh-setup β
β β Standard location for end users β
βββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββ€
β 3. Built-in prompts β <package>/runtime_assets/prompts/ β
β (fallback) β Minimal set bundled with pip install β
β β Ensures basic functionality always works β
βββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββ
Key methods to add to TNHContext:
get_prompt_search_paths() -> list[Path]: Returns all valid prompt directories in precedence orderget_primary_prompt_dir() -> Path | None: Returns highest-precedence directory that exists
2. Remove Module-Level Path Constants¶
Eliminate from src/tnh_scholar/__init__.py:
TNH_DEFAULT_PROMPT_DIR- replace with lazy resolution via TNHContextTNH_CONFIG_DIR- replace withUserRootLocator().resolve()TNH_LOG_DIR- derive from user root at runtime
These constants violate the style guide's "no module-level constants" rule and bypass runtime discovery.
3. Update GenAISettings to Use TNHContext¶
Replace hard-coded default with lazy resolution:
class GenAISettings(BaseSettings):
prompt_dir: Path | None = Field(
default=None, # Resolved at runtime, not import time
validation_alias=AliasChoices("PROMPT_DIR", "TNH_PROMPT_DIR"),
)
@model_validator(mode="after")
def resolve_prompt_dir_default(self) -> "GenAISettings":
if self.prompt_dir is None:
from tnh_scholar.configuration.context import TNHContext
context = TNHContext.discover()
self.prompt_dir = context.get_primary_prompt_dir()
return self
4. Consolidate PromptSystemSettings¶
Align PromptSystemSettings with GenAISettings - either:
- Option A: Deprecate PromptSystemSettings.tnh_prompt_dir in favor of GenAISettings.prompt_dir
- Option B: Have PromptSystemSettings also use TNHContext for its default
Recommendation: Option A - single source of truth for prompt directory in GenAISettings.
5. Bundle Minimal Built-in Prompts¶
Create src/tnh_scholar/runtime_assets/prompts/ with a minimal set of essential prompts:
_catalog.yaml(manifest)- 2-3 core prompts for basic tnh-gen functionality
This ensures pip install tnh-scholar works out of the box without requiring tnh-setup.
Consequences¶
Positive¶
- Consistent discovery: Prompts use the same three-layer model as registries (ADR-CF01).
- Package-installable: No hard-coded repo paths; works correctly with
pip install. - Clean dev/user separation: Developers get workspace prompts; users get
~/.configprompts. - No import-time failures: Path resolution happens lazily, not at module import.
- Single source of truth: One mechanism for prompt directory resolution across all subsystems.
Negative¶
- Migration effort: Existing code importing
TNH_DEFAULT_PROMPT_DIRneeds updating. - Lazy resolution complexity: First access to
GenAISettings().prompt_dirtriggers discovery. - Built-in prompts maintenance: Need to decide which prompts are "essential" for the fallback set.
Neutral¶
- tnh-gen's existing 5-level config precedence continues to work; this ADR affects the default value, not the override chain.
Alternatives Considered¶
A. Keep Module Constants, Make Them Optional¶
Make TNH_DEFAULT_PROMPT_DIR gracefully handle missing directories instead of raising at import.
Rejected: Doesn't solve the package-install problem or provide three-layer discovery.
B. Environment Variable Only¶
Require users to set TNH_PROMPT_DIR explicitly; no default discovery.
Rejected: Poor UX; tnh-setup already installs to a standard location that should "just work."
C. Prompt Discovery in GenAISettings Only¶
Add discovery logic to GenAISettings without extending TNHContext.
Rejected: Duplicates the discovery pattern already established in TNHContext for registries. Violates DRY.
Open Questions¶
-
Which prompts are "essential" for built-in fallback? Need to identify the minimal set that enables basic tnh-gen functionality without tnh-setup.
-
Should workspace prompts require a marker file? Currently workspace discovery uses
.tnh-scholaror.gitmarkers. Should prompt directories require explicit opt-in (e.g.,.tnh-promptsmarker)? -
PromptSystemSettings deprecation timeline: When should we fully deprecate
PromptSystemSettings.tnh_prompt_dirin favor of the unified approach?
Implementation Phases¶
Phase 1: Extend TNHContext (High Priority)¶
- Add
PromptPathBuilderclass tocontext.py - Add
get_prompt_search_paths()andget_primary_prompt_dir()toTNHContext - Create
src/tnh_scholar/runtime_assets/prompts/with minimal built-in set - Unit tests for prompt path resolution
Phase 2: Migrate GenAISettings (High Priority)¶
- Update
GenAISettings.prompt_dirto use lazy TNHContext resolution - Remove
from tnh_scholar import TNH_DEFAULT_PROMPT_DIRdependency - Update tnh-gen config_loader to work with new resolution
Phase 3: Clean Up Module Constants (Medium Priority)¶
- Remove
TNH_DEFAULT_PROMPT_DIR,TNH_CONFIG_DIR,TNH_LOG_DIRfrom__init__.py - Update all import sites across codebase
- Remove
FileNotFoundErrorraises at import time
Phase 4: Consolidate Settings Classes (Medium Priority)¶
- Deprecate
PromptSystemSettings.tnh_prompt_dir - Document the unified prompt directory strategy
- Update CLI tools to use consistent resolution
References¶
- ADR-CF01: Runtime Context & Configuration Strategy - Parent three-layer model
- ADR-A08: Config/Params/Policy Taxonomy - Settings vs Config distinction
- tnh-prompts repository - External prompt catalog