21 KiB
Appended Request Messages
Status
Accepted for future implementation. This roadmap records a concrete downstream requirement from Notarius and the Promptkit-native design selected for it. It describes future behavior only; the current public API and canonical documentation remain authoritative until the feature is implemented.
Purpose
Allow a Go consumer to execute an ordinary configured prompt with an explicit sequence of already-rendered chat messages appended after the prompt-definition messages. The capability should preserve Promptkit's normal preparation, inspection, hashing, validation, structured-output repair, capacity, cancellation, credential, error, and debug boundaries.
The immediate consumer is Notarius, which needs to resubmit the same configured prompt after semantic validation rejects an LLM-produced stage candidate, then append:
- an
assistantmessage containing the exact defective response; and - a
usermessage containing bounded application-owned feedback and a request for one corrected replacement response.
Promptkit should provide the safe message-composition mechanism. Notarius should continue to own semantic validation, stage retry budgets, correction wording, terminal policy, and the decision to start another completion.
Consumer Need
Promptkit v0.8 can perform bounded structural repair inside one Run or
RunPrepared operation. Its repairer correctly starts from the complete
original rendered prompt, appends the latest candidate as an assistant turn,
and adds a corrective user turn. That loop is intentionally driven by
Promptkit's own structural validators.
Notarius has a distinct outer workflow:
- Promptkit returns a structurally valid response.
- Notarius materializes a typed stage candidate.
- One or more application validators evaluate domain semantics.
- If the candidate is rejected and a Notarius stage attempt remains, Notarius makes a new Promptkit request with application-owned corrective turns appended after the ordinary configured prompt.
Each Run, Prepare, or PrepareExecution remains an independent operation
that resolves current sources and inputs. Promptkit does not retain or reuse a
base prompt across calls, so the ordinary prefix is identical across attempts
only when the selected definitions, inputs, variables, and other preparation
inputs remain identical. A consumer that requires a pre-execution equality
check can prepare each attempt and compare its opaque rendered-prompt hash
before calling RunPrepared. A reusable prepared conversation or frozen
cross-run prompt is outside this feature.
Today a RunRequest can select and parameterize a prompt definition, but it
cannot append downstream-owned messages. Registering a second prompt manifest
for every correction-capable operation would duplicate prompt identity,
profile defaults, input declarations, output contracts, and message assets.
Calling an injected model client directly would bypass Promptkit preparation,
capacity, structured output, repair, error adaptation, and provenance. Neither
workaround is a suitable long-term consumer contract.
Target Public API
Add an append-only field to RunRequest:
type RunRequest struct {
// Existing fields omitted.
AppendedMessages []RenderedMessage
}
Retain RenderedMessage.Role as a string for source compatibility and expose
the untyped string constants RoleDeveloper, RoleSystem, RoleUser, and
RoleAssistant. Their values are developer, system, user, and
assistant, respectively. The supported values and their semantics are part
of the public contract.
AppendedMessages contains fully rendered message values. Promptkit normalizes
and validates their roles as described below, but does not interpret their
content as Go templates, resolve input references inside it, or load it from
files. Nil and empty slices are equivalent.
Reusing RenderedMessage is preferred because it is already Promptkit's
public, stable representation of a provider-bound chat message and includes
the supported cache-control metadata. Its GoDoc should be generalized from a
definition-supplied message to an already-rendered chat message. A new
request-only message type would duplicate the same role, content, and cache
control contract without adding useful type safety.
The exact exported declarations and semantics, once implemented, belong in
GoDoc. The name AppendedMessages is intentional: placement is part of the
contract, whereas names such as AdditionalMessages leave ordering
ambiguous.
Composition Semantics
For Prepare, PrepareExecution, and Run, Promptkit should:
- perform ordinary prompt selection, input loading, variable processing, and prompt rendering;
- preserve every ordinary rendered message after its role has passed the shared canonical normalization, including content, cache-control value, order, and bytes, without rerendering or rewriting content;
- normalize and validate request-supplied roles, then defensively copy and
append
RunRequest.AppendedMessagesin caller-supplied order; and - treat the combined sequence as the effective rendered prompt for every later preparation and execution boundary.
The request-supplied messages always follow all prompt-definition messages. They cannot prepend, insert, replace, or delete definition messages. This narrow contract protects a stable prompt prefix for provider-side prompt caching and keeps prompt definitions as the canonical owner of ordinary prompt content.
Promptkit must not synthesize separators or normalize message content. Content, including empty or whitespace-only content, remains byte-for-byte caller data; whether a provider accepts it is provider policy. The effective session ID, selected profile, model settings, output contract, structured-output constraint, and input hashes retain their ordinary resolution rules.
InspectPrompt remains definition inspection and therefore does not accept or
report request-supplied appended messages.
Preparation, Hashing, And Inspection
Appended messages must participate in the same immutable preparation boundary as ordinary rendered messages:
PreparedRun.Messagescontains the complete combined sequence;PreparedExecution.Details().Messagescontains an independent defensive copy of that sequence;RunandRunPreparedsend exactly that sequence to the selected model client;RenderedPromptHashcovers the effective session ID and complete combined sequence, including appended-message cache-control metadata;PromptHashcontinues to identify only the selected prompt definition and therefore does not change merely because appended messages differ; and- input hashes continue to describe only named artifacts.
Two requests with identical prompt selection, inputs, variables, session, and appended messages should prepare equivalent message sequences and rendered prompt hashes. Changing any normalized appended role, content, order, or cache-control value should change the rendered prompt hash. The hash encoding must frame sessions, messages, and fields unambiguously rather than relying on separators that could also occur in caller-controlled content.
Prepared execution must freeze appended messages at preparation time. Caller
mutation after PrepareExecution returns must not affect its details or later
execution, and mutation of a value returned by Details must not affect the
opaque executable snapshot.
Interaction With Structured-Output Repair
The combined message sequence is the complete original prompt for one Promptkit operation. If Promptkit's structural validator rejects a generated candidate and a repair attempt is available, the existing repair workflow should begin from a fresh copy of that combined sequence and then append its own candidate and correction messages.
For the Notarius use case, the ordering may therefore be:
- ordinary configured prompt messages;
- Notarius's previous assistant response;
- Notarius's semantic-correction user message;
- Promptkit's latest structurally defective assistant response, if any; and
- Promptkit's structural-repair user message, if needed.
This is not a recursive retry policy. Promptkit still owns only its configured structural-repair budget within the current operation. It does not interpret the purpose of consumer-supplied messages, invoke application validators, or start a later Notarius stage attempt.
All initial and repair generation calls must continue through the ordinary prepared-execution, capacity, client, cancellation, credential, structured output, usage, and error-adaptation paths.
Message Validation And Provider Boundaries
Promptkit should validate appended messages before publishing a prepared value
or starting model generation. Validation should be shared by Prepare,
PrepareExecution, and Run and should reject:
- invalid UTF-8 in role or content;
- a role that, after normalization, is not
developer,system,user, orassistant; or - invalid cache-control metadata.
Role normalization is intentionally limited to trimming surrounding whitespace
and converting the result to lowercase. Promptkit performs no aliasing or
semantic role translation. In particular, it must not translate developer
to system or the reverse. system is generally the more portable choice
across heterogeneous OpenAI-compatible backends; developer remains supported
for providers and models that implement the newer instruction hierarchy. A
backend or model that rejects an otherwise supported role reports that through
the ordinary provider error path.
The same four-role vocabulary and normalization apply to prompt-definition
messages. This deliberately tightens the existing prompt format, which
currently accepts any nonblank role. tool is not supported because
Promptkit's text-only message value cannot express its required tool_call_id;
the deprecated function role likewise requires a name that the value cannot
represent. Those roles should be considered only as part of a deliberate
future tool-call message design.
Promptkit does not impose appended-message count, per-message size, aggregate size, estimated-token, or context-window limits, and it must not truncate caller content. Those limits are backend-, model-, and application-specific. Provider rejection for context length, role support, empty content, or any other provider rule should cross the existing generation-error boundary with its supported status and provider-detail information intact.
Invalid appended messages are invalid requests and should preserve the public
ErrInvalidRequest identity. Error text should identify the failing appended
message index and violated property without echoing its role or content. An
invalid role in a prompt definition remains a source-owned prompt-definition
failure through its ordinary public error category.
Ownership, Privacy, And Formatting
RunRequest.AppendedMessages is caller-owned. Promptkit must defensively copy
the slice and nested cache-control values before retaining them. Internal
domain conversion, prepared-run cloning, public result conversion, and
prepared-execution details must preserve the same non-aliasing guarantees as
ordinary rendered messages.
Appended content can contain model output, source excerpts, or application validation feedback and must be treated as sensitive prompt content:
RunRequest.StringandRunRequest.GoStringshould report only an appended message count, not roles or content;- errors must not reproduce appended content;
- no credential or secret value may be inferred, copied, or added to hashes beyond the existing prompt-content behavior;
- prepared values continue to expose complete rendered messages by design and remain subject to the consumer's data-handling policy; and
- Promptkit should not add logging, persistence, or a durable retry transcript for appended messages.
The field does not need a stable RunRequest JSON contract because
RunRequest has none today. The stable JSON representation of prepared values
must continue to include the final Messages sequence through its existing
contract.
Application-Neutral Boundary
This feature is a generic prompt-composition primitive. Promptkit should not add concepts such as semantic validator, rejection, stage, pipeline, correction reason code, or terminal validation policy to its public API.
The following remain downstream responsibilities:
- deciding whether a new request is warranted;
- choosing and enforcing the outer retry budget;
- retaining the exact earlier candidate;
- constructing, bounding, and redacting application feedback;
- determining which message roles and sequence express that workflow;
- keeping preparation inputs stable when an identical prefix is required, or checking prepared hashes before execution;
- aggregating semantic validation findings;
- recording application provenance; and
- deciding whether exhausted retries fail, reject, warn, or continue.
Promptkit owns only safe composition and execution of the effective request.
Documentation End State
In the target state, the capability is described by the following canonical owners:
- root GoDoc for
RunRequest,RenderedMessage,PreparedRun, and relevant engine operations, including the role constants and normalization contract; - the framework format reference for the tightened role vocabulary and normalization shared by prompt-definition messages;
- the Go consumer guide with one concise appended-message example and a warning that values are already rendered and potentially sensitive;
- the internal runner document for validation, composition, hashing, preparation, and structural-repair interaction;
- the internal source document for prompt-definition role normalization and source-owned failures;
- the internal model-client document only if its effective-message input contract needs clarification;
- the architecture policy only if implementation establishes a durable request-composition invariant not already covered by the public-facade and application-neutral-boundary rules;
- the OpenAI-compatible integration contract for supported provider-bound roles and provider rejection; and
- release guidance for the new minor version, including migration guidance for consumers with nonstandard prompt-definition roles.
The Go-only appended-message field does not belong in docs/formats.md.
However, the prompt-definition role contract does change and must be updated in
that canonical format reference. Profile and schema formats do not change.
Verification Expectations
Tests protect the public composition and ownership contract rather than private helper layout or exact diagnostic prose. Focused coverage establishes:
- nil and empty appended slices preserve current behavior;
- ordinary rendered messages remain an exact prefix and appended messages retain caller order;
- supported roles in both sources normalize by trimming and lowercasing, while
unsupported,
tool, andfunctionroles are rejected at the owning boundary; - message content, including empty and whitespace-only content, is preserved exactly and receives no Promptkit-owned count or size restriction;
Prepare,PrepareExecution, andRunagree on the combined sequence and rendered prompt hash;RunPrepareduses its frozen appended-message snapshot after caller mutation;Detailsand public conversions return independent copies;- changing a normalized appended role, content, order, or cache control changes
the
rendered prompt hash without changing
PromptHash; - invalid appended roles, UTF-8, and cache controls fail as invalid requests before model generation without echoing sensitive content;
- provider rejection of an otherwise structurally valid combined sequence retains the ordinary generation-error contract;
- the initial model call receives the combined sequence;
- structural repair reconstructs the complete combined sequence before adding Promptkit's repair turns;
- usage, capacity, cancellation, credentials, structured output, and error identity retain their existing behavior; and
StringandGoStringexpose only a count for appended messages.
One external-package contract test and focused internal runner tests should own most of this matrix. Extend the maintained offline run example only if it materially improves consumer understanding; do not turn the example into a retry framework or duplicate the contract tests.
Acceptance Criteria
- A consumer can append already-rendered messages through
RunRequestwithout defining a second prompt manifest or bypassingEngine. - Normalized ordinary prompt messages remain an unchanged, ordered prefix.
- The final sequence is prepared, hashed, reported in prepared details, frozen, executed, and repaired consistently across all engine workflows.
- Roles are normalized to and validated against the supported four-role vocabulary at both prompt-definition and appended-message boundaries.
- Appended values are structurally validated, defensively copied, and excluded from ordinary request formatting and error text without Promptkit-owned count, size, token, or context-window limits.
- Promptkit structural repair treats the combined sequence as its original prompt and retains all existing execution safeguards.
- Promptkit introduces no application-specific retry, validation, pipeline, or terminal-policy concepts.
- Existing consumers that omit appended messages and use supported roles retain current behavior; nonstandard prompt-definition roles are intentionally rejected under the tightened minor-version contract.
- Canonical GoDoc and consumer/internal documentation describe the implemented contract, and the complete maintainer validation workflow passes.
Non-Goals
- A Promptkit-owned semantic validation framework.
- A generic outer retry orchestrator.
- Automatic interpretation or formatting of downstream validation errors.
- Prepending, inserting, replacing, or deleting configured prompt messages.
- Treating appended content as a template or file reference.
- Accumulating conversation state across
Runcalls. - Freezing or reusing a base prompt across independent operations.
- A durable session or retry transcript store.
- PromptKit-owned message-count, byte-size, token, or context-window limits.
- Tool calls, tool results, deprecated function messages, or arbitrary custom message roles.
- Provider-specific branching for Notarius.
- Changes to profile or schema file formats.
Alternatives Considered
Paired Initial And Correction Prompt Definitions
A downstream application could register a second prompt definition that duplicates the initial manifest and adds candidate and correction inputs. This requires no Promptkit API change, but it duplicates prompt metadata and creates drift across prompt versions, profile defaults, output contracts, assets, and hashes. It also makes a generic request-composition need module-specific.
A Purpose-Specific Repair Request
Promptkit could expose an API accepting PreviousOutput and
ValidationErrors, mirroring its internal structural repair request. This
would initially fit Notarius, but it would make Promptkit interpret downstream
policy and would conflate application semantic retries with Promptkit
structural repair. A generic append-only message contract is smaller and more
reusable.
Direct Execution Of A Mutated Prepared Prompt
Promptkit could expose mutable prepared messages or accept a complete caller- constructed prompt for execution. That would weaken definition ownership, prepared-execution immutability, hashing, validation, and provenance, and would make it easy to bypass source and preparation guarantees. The requested API should compose messages before immutable preparation instead.
Direct Use Of The Model Client
Notarius could invoke an injected Promptkit-compatible model client directly. That bypasses the engine's profile resolution, backend admission, structured output, repair, error mapping, and prepared-execution contracts. It is not an acceptable downstream integration path.
A Semantic-Validation Callback
Promptkit could accept an application callback that validates each candidate
and drives another generation inside one Run. That could retain one frozen
prompt and aggregate usage automatically, but it would make Promptkit own
callback lifecycle, semantic diagnostics, application retry policy, and the
boundary between typed domain values and raw model output. Keeping semantic
validation and its outer loop downstream leaves the public API smaller and the
library application-neutral.
A Reusable Prepared Conversation
Promptkit could expose a reusable handle whose frozen base prompt accepts new turns across multiple executions. That would guarantee a byte-identical prefix and could support longer-lived chatbot workflows, but it would require a much larger lifecycle, credential-retention, concurrency, source-freshness, and history-ownership contract. The stateless append-only request primitive meets the demonstrated need without precommitting to those policies.
Complete Caller-Supplied Or Mutable Message Sequences
Promptkit could let a consumer replace, insert, or delete configured messages, or execute an entirely caller-constructed sequence. That would weaken prompt definition ownership, hashing, provenance, and the prepared-execution boundary. Append-only composition provides the needed flexibility while preserving configured messages as the canonical prefix.