Template Language Specification · v0.1 (Draft)
SPREP — Simple Prompt Response Embedded Pages
A declarative, server-side template language for embedding LLM prompts directly inside HTML. Reference implementation: the HPRC Framework.
v0.1 Draft .sprep.html Apache-2.0 © 2026 Rajesh Ramani1 · Overview
SPREP (Simple Prompt Response Embedded Pages) is a declarative template language for embedding generative-model (LLM) prompts directly inside HTML. A SPREP template is ordinary HTML augmented with a small set of elements that declare prompts, mark where their responses appear, bind data into them, and compose them together.
SPREP templates are executed by a conforming renderer during page rendering. The reference renderer is the HPRC Framework (HTML Prompt Response Construction).
Developers write SPREP templates. A renderer such as the HPRC Framework renders them.
Design tenets
- Declarative templates — say what to ask and where the answer goes; never business logic.
- Prompts are tacit — a
<prompt>is executable but never emitted into output. - Logic is external — conditions reference named rules; predicates live in host code.
- Capabilities are external — tools are referenced by name from an allowlist.
- Provider-independent — the language says nothing about the model vendor.
- Composable — one prompt may include another's response or text; the renderer orders execution automatically.
2 · File format
- Syntax base: an HTML superset. Non-SPREP markup is preserved verbatim in output.
- Conventional extension:
.sprep.html - Encoding: UTF-8.
- Media type (suggested):
text/html(tooling MAY useapplication/sprep+html).
3 · Namespaces & value resolution
<fill> and <param> resolve dotted paths
against a root namespace:
| Namespace | Source |
|---|---|
| application bindings | host-supplied data (mapping/object) |
request.query.* | request query-string parameters |
request.path.* | request path parameters |
request.method | the HTTP method (empty for non-HTTP) |
Path segments traverse mappings, object attributes and integer sequence indices. A missing path resolves to the empty string.
4 · Elements
SPREP defines five elements; all other markup is literal HTML.
4.1 <prompt> — executable, tacit
Declares a model call. Its body becomes the prompt text. It is never rendered.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
id | string | required | Unique; binds to <response> and <include>. |
model | string | renderer default | Model identifier passed to the provider. |
condition | rule name | none | Named rule gating execution; absent ⇒ always runs. |
temperature | number | provider default | Generation parameter. |
max_tokens | integer | provider default | Output token cap. |
async | yes|no | no | Prompts run sequentially by default; yes opts this prompt into concurrent execution with other async prompts in its level. |
cache | duration | none | TTL e.g. 24h; absent/0 ⇒ no caching. |
tools | name list | none | Comma-separated allowlist of tool names. |
Body may contain text and the directives <fill>,
<param>, <include>.
4.2 <response> — placeholder
Marks where a prompt's output is inserted, bound by id.
| Attribute | Type | Default | Meaning |
|---|---|---|---|
id | string | required | Must match a <prompt id="…">. |
render | yes|no | yes | no ⇒ generated but hidden (still available to <include response>). |
Multiple <response> elements MAY share an id (same output rendered in each place).
4.3 <fill> — data binding
<fill>customer.profile.name</fill>Resolves a dotted path. HTML-escaped in document output; inserted raw inside a prompt body (it is text for the model).
4.4 <param> — request-query shortcut
<param>product</param> <!-- equivalent to: <fill>request.query.product</fill> -->4.5 <include> — composition
<include prompt="summary"/> <!-- inserts the CONSTRUCTED PROMPT TEXT of "summary" -->
<include response="summary"/> <!-- inserts the GENERATED RESPONSE of "summary" -->An <include> creates a dependency on the referenced
prompt. The target MUST be a defined prompt id; an undefined reference is
an error (reported at parse time).
5 · Execution semantics
A conforming renderer performs one render pass:
- Parse into a node tree; index
<prompt>/<response>. - Normalize the request — convert whatever request object the web framework hands over (FastAPI, Flask, Django, or a plain dict) into one uniform
requestnamespace the template can read (request.query.*,request.path.*,request.method). This is what<param>and<fill>request.…</fill>resolve against. - Evaluate conditions — run each prompt's named
conditionrule against the bindings; skip the prompt on a false result (response = empty). An unregistered rule name SHOULD be an error; a rule that raises MAY be treated as skip. - Build the dependency graph from
<include>references. - Order execution by topological levels; a cycle is an error.
- Execute level by level — prompts run sequentially by default;
async="yes"runs concurrently with other async prompts in the level (a level may be sequential, concurrent, or a mix). Construct each prompt by resolving fills/params/includes (recursively, memoized). - Cache (if
cacheset) keyed on resolved prompt text + model + temperature + max_tokens + sorted tools; a hit skips the model. - Invoke the model via a provider-independent client with the resolved tools.
- Serialize — produce the final HTML by walking the document and emitting each node: a
<prompt>emits nothing; a<response id="X">emits prompt X's generated text (or nothing ifrender="no"); an<include response="X">emits X's text;<fill>/<param>emit their resolved value, HTML-escaped; all other markup is emitted unchanged.
6 · External contracts
- Rules — conditions are named only; no expression language in templates. Host supplies
{name → predicate(bindings) → bool}. - Tools — templates list an allowlist of names; host supplies
{name → tool}. The renderer resolves names (rejecting unknown) and exposes the tools to the model. A conforming renderer MAY execute the tool calls the model requests and feed results back; SPREP does not mandate an agent loop. (The reference implementation runs a single iteration — it executes the tool call(s), feeds results back, and renders the model's next response, or nothing if still calling tools after that iteration. A multi-step agent loop is on its roadmap.) - Cache — when a prompt sets
cache="…", the renderer stores that prompt's generated response and reuses it on later renders instead of calling the model again (saving latency and cost). The duration sets how long the entry stays valid:s/m/h/d/w(e.g.30m,24h) or bare integer seconds; absent or non-positive (0) ⇒ don't cache. Where it's stored is the renderer's choice via a pluggable cache backend — the reference implementation ships an in-process memory cache (interface open for others, e.g. Redis). The key is derived from the fully-resolved prompt text + model + temperature + max_tokens + tools. - Async — prompts run in sequential order by default;
async="yes"opts a prompt into concurrent execution with the other async prompts in its level (a level may be sequential, concurrent, or a mix). Dependent prompts always run after the prompts they include.
7 · Escaping & safety
<fill>/<param>output is HTML-escaped in the document — special characters (<,>,&,",') become their entities (e.g.<→<), so a value renders as literal text instead of being interpreted as markup (preventing broken layout and HTML/script injection, i.e. XSS). Inside a prompt body the same<fill>is inserted raw — there it is text for the model, not HTML.- Model responses at
<response>are emitted as-is; sanitize upstream if responses may contain untrusted markup. - Prompt bodies are tacit and never emitted, so prompt text does not leak into output.
8 · Conformance
A conforming SPREP renderer MUST:
- treat
<prompt>as tacit (never emit it); - resolve
<fill>dotted paths against bindings and therequestnamespace, and support<param>; - evaluate
conditionas a named rule lookup (no template-side expressions); - build the
<include>dependency graph and execute in dependency order, detecting cycles; - support
<response render="yes|no">, with hidden responses still available to<include response>; - pass
model,temperature,max_tokensand resolvedtoolsto the model client; - cache responses when a
cachedirective is present, keyed on the output-determining inputs.
A conforming renderer MAY add providers, cache backends, data sources or extensions, provided the above semantics are preserved.
9 · Reference implementation
The HPRC Framework is the reference implementation
(pip install hprc-framework, import hprc). See the
README, the User Guide
and the Architecture.
10 · Future extensions (non-normative)
These are not part of SPREP v0.1; they describe the planned direction (tracked in the HPRC roadmap), added as optional, non-normative elements/attributes:
<system>— a system-message directive prepended to prompt model calls.formaton<response>—text(escaped),markdown(→ sanitized HTML),html(sanitized).returns="json"on<prompt>— structured output whose fields fill many placeholders.<each>— iteration for rendering lists.<data>/<retrieve>— async data providers and RAG retrieval feeding prompts (retrieved_context).<live>— a region rendered as a placeholder + client hook, driven by a streaming endpoint (real-time widgets).
Renderer-level (not language) additions on the roadmap include a bounded multi-step tool
loop, MCP tool sources, an input message chain (prior_context), and returning
the executed turns to the caller.
Appendix A — Complete example
<h1><fill>customer.name</fill></h1>
<prompt id="summary" model="gpt-5" condition="is_premium_customer"
temperature="0.2" cache="24h" tools="crm_lookup,pricing_engine">
Customer: <fill>customer.name</fill> (tier: <fill>customer.tier</fill>)
Product of interest: <param>product</param>
Write a 2-sentence account summary.
</prompt>
<prompt id="upsell" model="gpt-5">
Given this summary: <include response="summary"/>
Suggest one upsell for "<param>product</param>".
</prompt>
<section><h2>Summary</h2><response id="summary"/></section>
<section><h2>Next step</h2><response id="upsell"/></section>upsell includes summary's response, so a renderer executes
summary first, substitutes its output into upsell, and emits
both — while neither <prompt> block appears in the final HTML.