Files
notarius/docs/roadmap/assets.md

367 lines
14 KiB
Markdown

# Centralized LLM Assets
## Purpose
Centralize Notarius's embedded LLM-facing assets in a top-level `assets/`
package so prompt declarations, prompt fragments, private structured-response
schemas, shared prompt text, and built-in LLM profiles are easy to find,
inspect, compare, and revise.
This is an ownership and repository-layout change, not a prompt redesign or a
change to any durable artifact contract. Modules continue to own all behavior
associated with their assets.
## Motivation
The D&D module family currently distributes its LLM assets across the packages
that consume them. Twelve D&D prompt consumers collectively embed 46 prompt
files. Their private response schemas, shared entity-reconciliation schema,
shared prompt fragments, and built-in `dnd-extraction` profile are likewise
spread across chunk, extract, normalize, shared, and registrar packages.
Co-location made each original module self-contained, but the growing family
now makes prompt inspection and cross-module editing unnecessarily difficult.
Prompt authors commonly need to:
- review all sibling prompts together;
- preserve exact shared bytes and cache-compatible message prefixes;
- compare declarations, instructions, tasks, and response schemas;
- identify duplicated or drifting language; and
- iterate on prompt content without navigating implementation packages.
A centralized, predictable asset tree makes those operations straightforward
while leaving executable behavior with the module packages.
## Decisions
### Top-Level Asset Package
All production LLM-facing assets will live beneath a repository-root
`assets/` directory. That directory will also be a Go package named `assets`
because Go embed patterns cannot embed files outside the embedding package's
directory tree.
The package will contain one Go source file, `assets/package.go`, unless a
future Go toolchain constraint makes that impractical. Its entire production
API will expose the embedded, read-only filesystem. The intended shape is
equivalent to:
```go
package assets
import (
"embed"
"io/fs"
)
//go:embed dnd generic
var embedded embed.FS
func FS() fs.FS { return embedded }
```
The exact embed patterns may reflect the final asset tree, but the package must
retain this minimal character. Adding a new asset domain may require extending
the embed directive; it must not require adding domain behavior to the package.
### Strict Data-Only Boundary
The `assets` package is a content container and dependency leaf. No business,
domain, framework, or PromptKit behavior may live there.
In particular, the package must not own:
- prompt IDs, schema IDs, schema names, or schema versions;
- prompt manifests, message order, cache controls, or input construction;
- module, schema, profile, or PromptKit registration;
- asset selection rules or module-to-path catalogs;
- parsing, validation, normalization, transformation, or hashing;
- checkpoint or fingerprint policy;
- fallback-profile precedence or model defaults; or
- imports of Notarius internal packages, PromptKit, or module packages.
It should expose no path-aware lookup helpers. Consumers obtain the root
read-only filesystem and use the standard library's `fs.Sub` to select the
smallest subtree they own. A module must not depend on an unrelated module's
asset subtree merely because both are available from the same embedded
filesystem.
The package's top-level location and exported accessor are an intentional
tradeoff in favor of human discoverability. They do not establish a supported
public extension API or transfer semantic ownership away from modules.
### Behavioral Ownership Remains With Modules
Each LLM-backed module continues to own:
- its prompt and response-schema identity constants;
- its ordered prompt-asset manifest;
- prompt input preparation and rendered message order;
- its response-schema definition and loading;
- prompt and schema registration with the shared LLM asset registry;
- output decoding and interpretation;
- its component fingerprint composition; and
- tests of its observable prompt and schema behavior.
The D&D shared package continues to own the manifest mechanism and the declared
set of reusable D&D prompt fragments. The D&D registrar continues to own family
composition and registration of the built-in fallback profile. The generic LLM
framework continues to own filesystem flattening, duplicate detection, schema
loading, PromptKit construction, and content hashing.
Physical storage is therefore centralized without creating a central package
that understands or composes every module.
### Asset Scope
The centralized tree includes embedded content whose direct purpose is to
configure or constrain an LLM interaction:
- PromptKit prompt declarations;
- module-owned prompt fragments;
- byte-identical shared prompt fragments;
- private JSON Schemas for structured LLM response envelopes;
- shared private response schemas such as D&D entity reconciliation; and
- built-in fallback LLM profiles.
The migration also includes the generic private LLM response schemas currently
embedded by the LLM framework. The framework may depend on the content-only
asset package for those generic files, but it must refer only to its own scoped
subtree and must not acquire D&D knowledge.
The following embedded files remain with their current owners:
- durable artifact schemas owned by D&D codec packages;
- framework-owned durable schemas such as chunk-map and evidence-context
contracts;
- the D&D spell catalog and other domain data that is not itself a prompt,
private LLM response schema, or built-in LLM profile;
- maintained configuration examples; and
- operator-supplied PromptKit profiles and other runtime files.
Private LLM response schemas must remain clearly distinguishable from durable
artifact schemas. Centralization must not turn a model transport envelope into
an external Notarius contract.
## Target Asset Tree
The target tree groups assets first by domain and then by conceptual module.
Directory names use readable kebab case; Go package names, module keys, prompt
IDs, and schema identities do not change to match the directory spelling.
```text
assets/
package.go
generic/
schemas/
dnd/
shared/
prompts/
profiles/
entity-reconciliation/
schemas/
scenes/
prompts/
schemas/
spells/
prompts/
schemas/
npcs/
extract/
prompts/
schemas/
normalize/
prompts/
combat-turns/
prompts/
schemas/
enemy-events/
prompts/
schemas/
item-events/
prompts/
schemas/
npc-interactions/
prompts/
schemas/
scene-descriptions/
prompts/
schemas/
locations/
extract/
prompts/
schemas/
normalize/
prompts/
location-occurrences/
prompts/
schemas/
```
Each prompt-bearing leaf retains predictable filenames for its declaration,
task, instructions, and any lane-specific fragments. Existing filenames should
be preserved where practical so this migration does not create gratuitous
content churn. Shared content must have one physical source file rather than a
copy in each consuming subtree.
Future LLM-backed domains and modules follow the same taxonomy. A future module
with both extraction and LLM-backed normalization assets uses explicit
`extract/` and `normalize/` children; a module with only one LLM-backed
operation may keep its prompt and schema directories directly beneath the
module directory.
## Filesystem And Registration Model
Consumers use `assets.FS()` plus `fs.Sub` to obtain a domain-, module-, or
asset-class-specific filesystem. Existing manifests continue to enumerate the
exact files used by each prompt. Registration continues through
`llm.AssetRegistry`, which presents PromptKit with its existing flattened
prompt, schema, and fallback-profile filesystems.
The virtual PromptKit layout is stable even though repository source paths
move. In particular:
- prompt IDs and declaration names remain unchanged;
- schema keys, IDs, versions, names, and registered filenames remain unchanged;
- fallback profile IDs, contents, and precedence remain unchanged;
- shared fragments remain mounted at the paths expected by existing prompt
declarations; and
- duplicate-path and missing-file failures continue to occur during
preparation.
No consumer receives a precomposed global prompt catalog from the `assets`
package. Module manifests and the D&D registrar remain the explicit composition
points.
## Prompt Caching And Fingerprints
The migration must preserve every prompt's rendered roles, bytes, input
placement, message order, and cache-control metadata. Merely moving source
files must not reduce backend prompt-cache compatibility or alter model-visible
content.
Each component fingerprint must remain scoped to the exact assets declared by
that component. An edit to one lane must not invalidate checkpoints belonging
to unrelated lanes. The implementation must not replace manifest-scoped
hashing with a digest of the complete root asset filesystem.
Current asset hashes include source paths as well as bytes, so moving the files
may produce a one-time checkpoint fingerprint change for affected components.
That safe invalidation is acceptable. Preserving old internal fingerprints is
not a reason to retain duplicate files, compatibility shims, or legacy asset
paths. After migration, changing an unrelated asset must not affect a
component's fingerprint.
## Architectural Documentation
Central storage refines the accepted domain-packaging decision in ADR-0004,
which currently says domain-specific prompt fragments and schemas live within
the domain implementation tree. Implementation must add a new ADR recording
the content-only root package, module ownership rules, alternatives, and
consequences, and mark only the conflicting asset-co-location portion of
ADR-0004 as superseded. Its remaining domain-first module packaging decision
continues to apply.
Once implemented, the canonical architecture and internal documentation must
describe current behavior:
- architecture documents the data-only asset-package boundary and dependency
direction;
- LLM internals document embedding, scoped filesystem consumption,
registration, and private-schema ownership;
- D&D internals document the domain asset taxonomy, shared-fragment ownership,
manifest-scoped fingerprints, and the convention for new modules; and
- the internal component overview links to the focused owner without
duplicating volatile paths.
The roadmap remains the only documentation of this unimplemented target until
the migration lands.
## Work Scope
Completing this feature requires:
- creating the top-level asset package and target directory structure;
- moving all in-scope files without editing their semantic content;
- replacing package-local `embed.FS` declarations for migrated assets with
scoped views of the central filesystem;
- updating module manifests, response-schema loaders, shared-fragment
resolution, generic schema loading, fallback-profile registration, and asset
hashes to use those scoped views;
- removing obsolete module-local prompt, private-schema, and profile asset
directories and their embedding files;
- preserving module-local behavioral declarations and registration methods;
- updating tests and documentation at their durable ownership boundaries; and
- recording the architectural decision described above.
## Verification Expectations
Verification should protect behavior and architectural boundaries rather than
freeze prompt prose or count files. The completed migration must demonstrate
that:
- all registered prompts and private response schemas prepare successfully;
- all built-in fallback profiles remain available with unchanged effective
configuration;
- rendered prompt messages and cache controls are unchanged for representative
extraction, chunking, and normalization prompts;
- shared prompt fragments come from one physical source and render identically
wherever reused;
- schema loading retains the existing identities and content digests;
- duplicate, missing, or malformed assets still fail at the established
preparation boundary;
- component fingerprints use only their declared assets;
- D&D family and production composition tests pass;
- no package-local migrated copies remain; and
- `assets/` contains only the single minimal Go source file and in-scope data
files, with no business logic or internal/PromptKit dependencies.
Tests must follow the testing policy: do not add exact prompt-prose snapshots,
asset-count assertions, source-path snapshots, or other change-detector tests.
Use existing preparation, rendering, registration, schema, fingerprint, and
composition boundaries to protect meaningful behavior.
Repository-wide validation includes:
```sh
go test ./...
go vet ./...
go build ./cmd/notarius
```
## Non-Goals
This work does not:
- rewrite, consolidate, or otherwise tune prompt content;
- change prompt message order, caching policy, or profile defaults;
- change private or durable schema shapes;
- move durable schemas or the spell catalog into the root package;
- add runtime prompt overrides or live filesystem reloading;
- expose asset paths as configuration or a supported public API;
- replace the existing LLM asset registry or PromptKit filesystem boundary;
- centralize module behavior, registration, or identity constants; or
- preserve reuse of checkpoints created with the pre-migration internal asset
paths.
An optional development-time filesystem overlay may be considered separately
if prompt iteration without rebuilding becomes valuable. It requires distinct
precedence, provenance, fingerprint, reproducibility, and security decisions
and is not implied by this repository-layout change.
## Target End State
Prompt authors can inspect every embedded LLM interaction asset from one
predictable top-level tree. Reusable prompt content has one physical owner, and
private LLM schemas are visibly separated from durable artifact contracts.
The `assets` Go package consists only of `package.go`, embeds the content, and
exposes one read-only filesystem accessor. It contains no application logic and
has no Notarius-internal or PromptKit dependencies. Modules select scoped
subtrees and retain complete ownership of prompt semantics, schema identity,
registration, interpretation, and fingerprints. The framework remains
domain-neutral, the D&D registrar remains the family composition root, and all
observable prompt, schema, profile, and pipeline behavior is unchanged apart
from the accepted one-time invalidation of checkpoints whose fingerprints
include moved source paths.