The Prompt DSL
# The Prompt DSL
Audience: prompt authors and recipe authors who bind prompts. Purpose: the
.promptfile format, the registry, and how the runtime resolves prompts. Prerequisites: Configuration for where prompts live; RECIPE_DSL forimport prompt.
# What a prompt file is
A .prompt file is a unit of model-facing text owned by the prompt layer, not
by agent code: front matter (identity, tags, variable declarations) plus a
markdown body.
System and user prompts live in prompt files; Go code must not embed them.
- Extension:
.prompt. - Identity: the
idstatement in the front matter; the file path does not define identity. - Encoding: UTF-8, front matter first.
# Front matter
Line-oriented statements between --- fences:
---
schema framework.prompt/v2
id agent.react.default
tag "system"
tag "agent"
tag "react"
var tools = ""
var guidance = ""
---| Statement | Form | Notes |
|---|---|---|
schema |
schema framework.prompt/v2 |
Exactly this value; anything else is a load error |
id |
id <dotted.name> |
Required; duplicate IDs across the registry are a hard load error naming both paths |
tag |
tag "word" |
Repeatable, deduplicated; used for filtering |
var |
var name = "default" |
Repeatable; the default must be a quoted string; an empty default records a warning |
Unknown statements, a missing schema, or a missing id are load errors.
# Body: markdown with variable substitution
The body is CommonMark, rendered block-by-block and trimmed.
- Variable references are single-brace:
{name}. A{{name}}reference parses as an invalid variable and fails validation. - Resolution order: caller-supplied runtime variables first, then the declared default.
- An unknown reference (no override, no default) is a resolve error.
- Escape braces with
\{and\}. - A body line that is only a variable reference substitutes multi-line values fine; the substituted text is not re-parsed as markdown.
Code spans are verbatim. Inline code spans render without substitution, so protect literal JSON with them:
Return ONLY JSON with fields:
`{"thought":"short reasoning","action":"tool|complete"}`Unprotected braces with a non-identifier inside are invalid variable references and fail resolution; always code-span JSON schemas.
# Placement and loading order
The registry loads, in order:
<workspace>/templates/prompts/framework/<workspace>/templates/prompts/agents/<workspace>/templates/prompts/named/<workspace>/relurpify_cfg/prompts/
Missing directories are skipped.
For a normal user workspace only relurpify_cfg/prompts/ exists: doctor
materializes the shipped corpus there (agents/, framework/, named/,
29 files), and materialization never overwrites an existing file, so your
edits win.
Re-declaring an ID that already exists in another loaded layer is a hard
error naming both paths; pick a new dotted ID instead.
# Who resolves what
| Seam | Prompt ID | Variables injected |
|---|---|---|
| React system prompt | agent.react.default |
tools, guidance |
| React user prompt | task override, else agent.react.default.user |
tools, instruction |
Recipe goal prompt <alias> |
the imported ID | step context including instruction |
| Chainer links | the link’s prompt ID | the link’s from keys |
Known issue: a recipe goal prompt currently renders the file twice and the
user’s request never reaches the model; prefer
goal """...{{ index . "task.instruction" }}...""" until it is repaired
(see RECIPE_DSL
).
Resolution is deterministic: the same inputs render the same text.
Each resolution emits telemetry (prompt.resolved, prompt.resolve_failed),
which makes a silent prompt failure visible.
# The shipped corpus
| ID | Role |
|---|---|
agent.react.default |
React system prompt ({tools}, {guidance}) |
agent.react.default.user |
React user prompt ({tools}, {instruction}; the JSON decision schema is code-span-protected) |
agent.{chainer,euclo,generic,pipeline}.default |
Paradigm and agent defaults |
rewoo.plan.v1, rewoo.synth.v1 |
ReWOO planner and synthesizer prompts |
framework/* |
Framework blocks: base system, capability catalog/execution/selection, error recovery, tool formatting, validation |
named/euclo/* |
Euclo prompts importable from recipes |
The runtime resolves some IDs by fixed name (agent.react.default).
Deleting or re-id-ing such a prompt degrades the react loop to its inline
fallback and emits prompt.resolve_failed in telemetry; treat that line as a
regression signal.
# Validation
- Load time: malformed front matter, unknown statements, invalid identifiers,
and duplicate IDs are hard errors; empty
vardefaults and declared-but- unused variables are warnings. - Resolve time: unknown variable references and invalid reference syntax are errors.
relurplint --check prompts --workspace .runs the file-level checks without a runtime.
# Authoring workflow
- Author
relurpify_cfg/prompts/<agents|framework|named>/.../<name>.prompt. - Lint:
relurplint --check prompts --workspace . - If a recipe will import it, bind it there:
import prompt <id> as <alias>plusgoal prompt <alias>(mind the double-render caveat above). - Run a turn and confirm
prompt.resolved(notprompt.resolve_failed) in.relurpify_state/telemetry/otlp/*.jsonl(OTLP-JSON log records; see RELURPISH ).
# Changing the shipped defaults
Editing the shipped defaults is framework work: change the embedded bundle at
userconfig/templates/embedfs/prompts/, run make generate-config, and let
the drift gate verify the materialized tree.
Editing a shipped prompt in your workspace is simpler: change the
relurpify_cfg/prompts/ copy; materialization will not overwrite it.