Skip to content

§2 File Format

A .prompty file consists of an optional YAML frontmatter block followed by a markdown body. The frontmatter is delimited by --- or +++ markers:

---
<YAML frontmatter>
---
<markdown body>

The markdown body becomes the instructions field on the loaded PromptAgent.

If no frontmatter markers are found, the entire file content MUST be treated as the body (instructions only), with no frontmatter properties set.

If frontmatter markers are found but the content between them is not valid YAML, implementations MUST raise a parse error (ValueError or equivalent).

Implementations MUST split frontmatter from body using the following contract:

Reference regex:

^\s*(?:---|\+\+\+)(.*?)(?:---|\+\+\+)\s*(.+)$

With flags: dotall (. matches newlines) and multiline.

  • Group 1: YAML frontmatter content (between the delimiters).
  • Group 2: Markdown body (everything after the closing delimiter).

Behavior:

  1. If the content does not start with a frontmatter delimiter (--- or +++), return {instructions: <entire content>} — no frontmatter is parsed.
  2. If a delimiter is found at the start but no closing delimiter matches, implementations MUST raise a ValueError (malformed frontmatter).
  3. Leading whitespace before the opening delimiter is permitted.
  4. The opening and closing delimiters need not match (e.g., --- may close with +++), but implementations SHOULD use matching delimiters.

Test vectors (normative — tie-breaker for ambiguous behavior):

# Vector 1: Standard frontmatter
input: "---\nname: test\n---\nHello world"
frontmatter: "name: test"
body: "Hello world"
# Vector 2: No frontmatter
input: "Just a prompt with no frontmatter"
frontmatter: null
body: "Just a prompt with no frontmatter"
# Vector 3: Empty frontmatter
input: "---\n---\nBody only"
frontmatter: ""
body: "Body only"
# Vector 4: Whitespace before delimiter
input: " ---\nname: test\n---\nBody"
frontmatter: "name: test"
body: "Body"

The following properties are valid at the top level of the frontmatter YAML. The types correspond to the TypeSpec-generated data model.

PropertyTypeDescription
namestringPrompt name identifier
displayNamestringHuman-readable display name
descriptionstringPrompt description
metadatadictArbitrary metadata (authors, tags, version, etc.)
modelModel or stringModel configuration (see §2.4)
inputsProperty[] or dictInput schema definition (see §2.7)
outputsProperty[] or dictOutput schema definition (see §2.7)
toolsTool[]Tool definitions (see §2.9)
templateTemplateTemplate engine configuration (see §2.8)

Unknown top-level properties SHOULD be preserved in metadata or ignored. Implementations MUST NOT raise an error for unknown properties.

The model property configures the LLM provider and parameters.

Shorthand form: When model is a plain string, it is equivalent to Model(id: <string>).

# Shorthand
model: gpt-4
# Equivalent expanded form
model:
id: gpt-4

Full Model properties:

PropertyTypeDefaultDescription
idstringModel identifier (e.g., gpt-4, text-embedding-3-small)
providerstringProvider key: openai, azure, anthropic, foundry, etc.
apiTypestring"chat"API type: chat, embedding, image, responses
connectionConnectionAuthentication and endpoint configuration
optionsModelOptionsModel parameters (temperature, tokens, etc.)

The connection property under model defines how to authenticate with the LLM provider. Connection types are discriminated by the kind field:

KindRequired FieldsDescription
keyendpoint, apiKeyAPI key authentication
referencenameNamed reference to external config
remoteendpoint, targetRemote connection with auth mode
anonymousendpointNo authentication
foundryendpointMicrosoft Foundry connection
oauthendpoint, authenticationModeOAuth-based authentication
PropertyTypeDescription
temperaturefloatSampling temperature (0.0–2.0)
maxOutputTokensintegerMaximum tokens in the completion
topPfloatNucleus sampling threshold
topKintegerTop-K sampling (provider-dependent)
frequencyPenaltyfloatFrequency penalty (-2.0–2.0)
presencePenaltyfloatPresence penalty (-2.0–2.0)
seedintegerRandom seed for reproducibility
stopSequencesstring[]Sequences that stop generation
allowMultipleToolCallsbooleanAllow parallel tool calls
additionalPropertiesdictProvider-specific options (passthrough)

Input and output schemas define the data contract for the prompt. Each property in the schema is a Property object:

FieldTypeDescription
kindstringType discriminator (see below)
descriptionstringHuman-readable description
requiredbooleanWhether the input is required (default: false)
defaultanyDefault value when input is not provided
exampleanyExample value (for documentation/testing)
enumValuesany[]Allowed values (enum constraint)

Property kinds:

KindDescriptionCategory
stringText valueSimple
integerWhole numberSimple
floatDecimal numberSimple
booleanTrue/falseSimple
arrayList of valuesSimple
objectNested dictionary/mapSimple
threadConversation history (Message[])Rich
imageImage data (URL or base64)Rich
fileFile data (URL or base64)Rich
audioAudio data (URL or base64)Rich

Rich kinds (thread, image, file, audio) receive special handling during rendering — see §5 Rendering for details.

Scalar shorthand: When a property value is a plain scalar instead of a Property object, it MUST be interpreted as Property(kind: <inferred>, default: <value>).

# Shorthand
inputs:
firstName: Jane
# Equivalent expanded form
inputs:
firstName:
kind: string
default: Jane

Kind inference from scalar type: string → "string", integer → "integer", float → "float", boolean → "boolean", list → "array", dict → "object".

The template property selects the template engine and parser.

FieldTypeDefaultDescription
formatFormat{kind: "jinja2"}Template engine to use
parserParser{kind: "prompty"}Output parser to use

Shorthand form: When template is a plain string, it is the format kind:

# Shorthand
template: jinja2
# Equivalent
template:
format:
kind: jinja2
parser:
kind: prompty

When template is omitted entirely, implementations MUST default to {format: {kind: "jinja2"}, parser: {kind: "prompty"}}.

Tools are defined as a list under the tools frontmatter property. Each tool has a kind discriminator that determines its type and required fields.

Common fields (all tool kinds):

FieldTypeDescription
namestringTool name (unique within the agent)
kindstringTool type discriminator
descriptionstringHuman-readable tool description
bindingsdictStatic parameter bindings (see §2.9.1)

Tool kinds:

KindTypeAdditional Fields
functionFunctionToolparameters (list[Property]), strict (boolean)
promptyPromptyToolpath (string), mode (single or agentic)
mcpMcpToolconnection, serverName, approvalMode, allowedTools
openapiOpenApiToolconnection, specification (path or URL)
*CustomToolconnection, options — wildcard catch-all

Any kind value that does not match function, prompty, mcp, or openapi MUST be loaded as a CustomTool (the wildcard * catch-all).

Bindings are static parameter values that are injected when a tool is executed but MUST NOT appear in the tool definition sent to the LLM. This allows prompts to bind context (user IDs, session tokens, API keys) without exposing them in the tool schema.

tools:
- name: get_user_orders
kind: function
description: Get orders for a user
parameters:
- name: user_id
kind: string
required: true
- name: limit
kind: integer
default: 10
bindings:
user_id: ${env:CURRENT_USER_ID}

Binding behavior:

  1. When converting tools to wire format (§7 Wire Format), parameters listed in bindings MUST be stripped from the tool’s parameter schema sent to the LLM.
  2. When executing a tool call from the LLM’s response, bound parameters MUST be injected into the tool’s arguments before invocation.
  3. If the LLM provides a value for a bound parameter, the binding MUST take precedence.

The prompty tool kind allows one .prompty file to invoke another as a tool:

tools:
- name: summarize
kind: prompty
path: ./summarize.prompty
mode: single
description: Summarize a block of text
FieldTypeDefaultDescription
pathstringRelative path to the .prompty file
modestring"single"single: one-shot call. agentic: full agent loop

When the LLM invokes this tool:

  1. Load the referenced .prompty file via load(path).
  2. If mode is "single": call invoke(path, arguments) — one pass, no tool loop.
  3. If mode is "agentic": call invoke_agent(path, arguments) — full agent loop with any tools defined in the referenced prompty.
  4. Return the result as the tool’s output.

Role markers in the markdown body define message boundaries. Each marker produces a new message in the parsed Message[] list.

Recognized roles: system, user, assistant

Syntax:

role:
role[key=value, key2=value2]:

Role markers MUST be on their own line (possibly with leading whitespace or a leading # for markdown heading compatibility). Role names are case-insensitive.

Examples of valid role markers:

system:
user:
assistant:
user:
# system:
assistant[nonce=abc123]:

Content between markers becomes the message content. Leading and trailing blank lines within a message MUST be trimmed.

Default role: If the body has content before any role marker, implementations MUST treat it as a system message.

String values in the frontmatter MAY contain ${protocol:value} references that are resolved during loading (§4 Loading).

Resolves to the value of environment variable VAR_NAME.

  • If VAR_NAME is not set, implementations MUST raise a ValueError (or equivalent).

Resolves to the value of VAR_NAME, or default if the variable is not set.

  • The default is everything after the second : in the expression.

Loads file content relative to the .prompty file’s directory.

  • If the file has a .json extension, it MUST be parsed as JSON.
  • If the file has a .yaml or .yml extension, it MUST be parsed as YAML.
  • All other extensions MUST be read as raw text.
  • If the file does not exist, implementations MUST raise a FileNotFoundError.

Resolution scope: References are resolved in ALL string values throughout the frontmatter dict tree, not just at the top level. Implementations MUST walk the entire dict recursively.