Files
notarius/docs/internal/llm.md

11 KiB
Raw Blame History

LLM Runtime Internals

internal/framework/llm is Notariuss provider-independent structured completion boundary. It adapts framework requests to PromptKit, bounds provider calls, assembles registered prompt and schema assets, records selected profiles, and redacts provider errors. The architectural boundary is defined in Architecture; profile sources, credentials, and concurrency settings belong in Configuration and Configuration.

Structured Completion Boundary

Modules and LLM-backed validators depend only on contracts.StructuredLLMClient. A completion request supplies a prompt ID and version, optional profile and session IDs, named input material, variables, and a caller-owned decode target. The successful response returns the validated raw structured bytes together with non-secret provider, model, profile, and token metadata.

The caller owns the domain behavior: it chooses the prompt, prepares inputs, selects the private response schema, and interprets the decoded result. The adapter does not own source evidence, artifact conversion, normalization, or durable schemas. Those responsibilities remain with the module and its integration contract.

PromptKitClient validates the request target and prompt identity, maps each named material to a PromptKit inline artifact while preserving its origin URI, maps the trimmed request session to PromptKit's direct per-run session field, retains the same value as the session_id prompt variable for maintained prompt compatibility, forwards profile selection, then prepares and runs the prompt. The direct field is authoritative for provider session behavior. A session ID is a stable, non-secret correlation identifier and may be exposed to providers and provider observability. The adapter returns PromptKits validated raw bytes rather than re-encoding the decoded target. An empty optional material is represented as one space so its named input is retained by PromptKit.

Client construction may also receive a run-wide reasoning-effort override from the CLI factory boundary. The adapter copies the caller-owned pointer and creates a fresh PromptKit execution override for each request: a nil pointer inherits the selected profile, a non-empty value replaces it, and an empty value clears inherited reasoning. The CLI's mutually exclusive --reasoning-effort and --clear-reasoning-effort controls select those states. With neither flag, profile behavior remains unchanged. Because production constructs one shared client, the selected state applies uniformly to module calls, retries, and LLM-backed validators for the whole run.

An empty request profile lets the prompt select its configured default. The CLI prepares every explicitly selected binding profile before a run begins, so a missing explicit profile fails before stage execution. Calls record the profile actually selected by PromptKit. The recorder trims and deduplicates non-secret profile identity, provider, model, selected backend ID, and effective reasoning values for manifest use. Entries that differ in backend or reasoning remain distinct and deterministically ordered. Endpoint-only profiles retain an empty backend ID, which the published JSON omits. Successful completion responses and recorded profile manifests identify the adapter provider as promptkit.

Before execution, the adapter also contributes a non-secret checkpoint fingerprint for the effective PromptKit profile source. It combines the identity of PromptKit's compiled-in profile catalog with a deterministic digest of every YAML profile in the configured profile directory, or of the configured profile file. The fingerprint contains neither profile content nor source paths. It covers both explicit binding profiles and prompt-selected defaults, so changing a model or other profile setting cannot reuse checkpoints created under the prior profile source. This cache identity is independent of durable profile provenance: run manifests continue to list only profiles actually observed during LLM calls.

Shared Provider-Call Limit

Production construction creates one PromptKit client and wraps it in one scheduled client. The scheduler has a fixed, positive permit limit, serves queued calls in FIFO order, and removes a queued call when its context is cancelled. A granted permit is released exactly once on every completion path.

The scheduled wrapper surrounds every CompleteStructured call, so concurrent lanes, pipeline retries, and LLM-backed validators share the same provider-call ceiling. This ceiling is independent of pipeline worker concurrency; changing worker counts cannot exceed the configured LLM limit. The configuration field and its effective default are owned by Configuration.

Prompt And Schema Assets

An AssetRegistry collects prompt and schema filesystems from production module families. It flattens registered roots into the PromptKit filesystems and rejects invalid roots, unreadable assets, duplicate paths, and missing prompt or schema files during preparation. The frameworks promptfs helper combines module-owned prompt files with reusable domain fragments without making the framework depend on D&D content.

Each LLM-backed module owns its prompt declaration, package-specific assets, and private response schema. Shared D&D wording is owned by the D&D shared asset package; the detailed D&D conventions are in D&D Module Internals. The mounted prompt assets used by a module also determine its prompt fingerprint. Schema loaders validate JSON, attach identity and digest metadata, make defensive copies, and expose diagnostics without raw schema bytes.

Private response schemas validate a model transport envelope. They are not the durable artifact schema and should not be documented as an external wire contract. Durable formats and compatibility rules remain in the integration contracts.

Prompt Maintenance And Backend Caching

Prompt message order and shared asset bytes are runtime behavior. Backend cache reuse depends on identical preceding roles, rendered bytes, and cache-control metadata—not merely equivalent meaning. Keep reusable shared assets byte-identical and preserve each prompts declared ordering and cache controls when editing it.

For sibling prompts that can reuse the same source material, order universal shared context first, request source material next, and module-specific suffixes last. Put a cache boundary at a reusable prefix that is useful to the backend. Redundant intermediate cache boundaries do not extend that reusable prefix and add no value.

Prompt-family owners may choose a different sequence when their inputs and reuse pattern differ. The D&D familys extraction, scene-chunking, and NPC normalization policies are maintained in D&D Module Internals. Do not add tests that enforce prompt prose; prompt tests should verify the meaningful input placement and cache controls of the prompt being changed.

Validation, Repair, And Retries

PromptKit performs prompt rendering, provider execution, and the prompts structured-output validation. The adapter reports an empty result, validation failure, empty structured body, or decode failure as ErrInvalidStructuredOutput, while retaining the returned raw bytes and debug material when they exist. Provider failures remain operational errors rather than output-validation failures.

Prompt-declared repair is executed within PromptKits structured-output flow. The current production D&D prompt manifests set repair attempts to zero. That setting does not replace pipeline retry behavior: a bindings configured retry count reruns its stage attempt after an error or rejection, and an exhausted rejection is a recorded output rather than a provider error. The pipeline owns attempt lifecycle, validation chains, and retry diagnostics; see Pipeline Internals and the binding reference.

Timeout Ownership

The caller context remains the outer cancellation authority. PromptKit applies a positive effective generation timeout as an inner request deadline; an explicit zero disables only that generation deadline. The HTTP client timeout is a separate transport-wide cap. Notarius forwards the caller context and does not install another timeout wrapper around PromptKit.

The selected PromptKit profile owns generation settings. Notarius binding retries remain outside the adapter and repeat the complete module operation and validation chain. PromptKit does not add a provider retry loop. Operator-facing behavior is summarized in Operations, and the pinned upstream contract is identified in PromptKit Integration.

Observability And Redaction

When debug recording is enabled, the pipeline decorates the shared client. The wrapper records prepared prompt and response material, timing, selected profile and backend, effective model parameters, and call identifiers in the runs debug bundle, including material available from a failed structured completion. Effective parameters use PromptKit's stable lower-case JSON field names and may include backend_id. For a successful completion, a debug-write failure is surfaced; when the completion already failed, its call error remains the result. Debug-bundle location, retention, and handling are operational concerns documented in Operations.

Run manifests receive selected profile summaries, including optional effective backend and reasoning provenance, and component identities—not prompt, schema, source, reference, or response content. The published field semantics belong to the JSON output contract. Provider error text is wrapped with prompt context and bearer credentials are redacted before it crosses the runtime boundary. Known-secret redaction is available to other runtime collaborators; it does not make prompt or response contents safe for general logging.

Failure Boundaries

  • Construction fails for missing asset registries, mutually exclusive profile sources, invalid asset registration, or a non-positive scheduler limit.
  • Preparation failures, unavailable explicit profiles, provider failures, and context cancellation propagate to the calling stage with context.
  • Malformed or schema-invalid provider output is classified separately as invalid structured output so the module or pipeline can apply its own retry and rejection policy.
  • Domain semantic checks, evidence decisions, and deterministic normalization run outside the provider adapter.

Focused Verification

Read the LLM adapter, scheduler, asset registry, schema loader, and redaction tests when changing this boundary. Prompt changes also require the owning modules asset tests, and retry or debug changes require focused pipeline or CLI coverage. The focused runtime and D&D checks are:

go test ./internal/framework/llm/... ./internal/modules/dnd/...