Skip to content

§12 Cross-Cutting Concerns

All pipeline functions MUST have async variants. Pipeline functions SHOULD also have sync variants where the language supports synchronous execution.

The following functions MUST exist in both sync and async forms:

SyncAsync
loadload_async
renderrender_async
parseparse_async
prepareprepare_async
runrun_async
invokeinvoke_async
processprocess_async
invoke_agentinvoke_agent_async

Naming convention: function_name for sync, function_name_async for async. Language-idiomatic alternatives are acceptable (e.g., TypeScript MAY provide only async functions since its ecosystem is async-first).

Registry operations (register_connection, get_connection, register_tool, get_tool, clear_cache, etc.) are sync-only. They perform in-memory lookups and MUST NOT perform I/O.

The validate_inputs function MUST be called as part of prepare(), before rendering:

function validate_inputs(agent, inputs) → dict:
validated = shallow_copy(inputs)
for prop in agent.inputs:
if prop.name not in validated:
if prop.default is not null:
validated[prop.name] = prop.default
elif prop.required:
raise ValueError("Missing required input: " + prop.name)
// else: optional with no default — omit
return validated

Behaviour requirements:

  • MUST fill missing inputs from default when available.
  • MUST raise ValueError for missing inputs that are required and have no default.
  • MUST NOT raise for optional inputs with no value and no default — they are simply omitted from the validated dict.
  • MUST NOT use example as a runtime value under any circumstances — it exists solely for documentation and tooling. Using example as a fallback is the fastest path to unexpected behavior.
  • MUST NOT type-check input values (the template engine handles coercion).
  • MUST NOT reject extra inputs not declared in inputs (they are passed through to the template).

Loading .env files is an application-level concern. Runtime libraries MUST NOT automatically load .env files as a side effect of loading a .prompty file.

Guidance for application authors:

  • Load .env files at application startup, before calling load().
  • Look for .env in the .prompty file’s parent directory or the project root.
  • Format: standard dotenv (KEY=VALUE, one per line, # comments, optional quoting).
  • .env files MUST NOT override environment variables that are already set in the process environment.

Implementations MUST raise appropriate errors for the conditions listed below. This specification defines what error to raise and when; the specific exception class SHOULD follow language idioms.

StageConditionError TypeMessage Pattern
LoadFile not foundFileNotFoundError"Prompty file not found: {path}"
LoadInvalid YAML frontmatterValueError"Invalid frontmatter YAML: {details}"
LoadMissing env var (no default)ValueError"Environment variable '{name}' not set"
LoadReferenced file not found (${file:})FileNotFoundError"Referenced file not found: {path}"
RenderUndefined template variableValueError"Undefined template variable: {name}"
RenderTemplate syntax errorValueError"Template syntax error: {details}"
ParseRole marker nonce mismatch (strict mode)ValueError"Role marker nonce mismatch (possible injection)"
ExecuteUnsupported apiTypeValueError"Unsupported API type: {type}"
ExecuteConnection failureConnectionErrorProvider-specific message
ProcessUnexpected response formatValueError"Unexpected response format"
Agent LoopTool not registeredValueError"Tool not registered: {name}"
Agent LoopMax iterations exceededRuntimeError"Agent loop exceeded {n} iterations"
Agent LoopModel refusalValueError"Model refused: {message}"
DiscoveryNo implementation for keyInvokerError"No {component} registered for key: {key}"

RICH_KINDS = { thread, image, file, audio }

These are input property kinds that contain structured data requiring special handling during rendering and parsing. They MUST NOT be passed directly to the template engine as raw values.

KindContainsRenderingParsingWire Format
threadMessage[]Nonce replacementExpanded back to Message[]N/A (already messages)
imageImage data or URLNonce replacementPreserved as nonceResolved to ImagePart
fileFile data or URLNonce replacementPreserved as nonceResolved to FilePart
audioAudio data or URLNonce replacementPreserved as nonceResolved to AudioPart

Nonce format: All rich kinds use the same nonce format:

__PROMPTY_THREAD_<hex8>_<name>__

Where <hex8> is 8 random hexadecimal characters and <name> is the input property name. The nonce MUST be unique per render invocation.

Purpose: The nonce prevents template engines from interpreting or mangling structured data. During rendering, the structured value is replaced with a nonce string. After parsing, the nonce is resolved back to the appropriate content:

  • Thread nonces are expanded during the parse/prepare phase into the actual Message[] sequence, spliced into the message list at the nonce’s position.
  • Image, file, and audio nonces are resolved during wire-format conversion (§7 Wire Format) when the actual ImagePart, FilePart, or AudioPart content types are constructed.