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 Ramani

1 · 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 use application/sprep+html).

3 · Namespaces & value resolution

<fill> and <param> resolve dotted paths against a root namespace:

NamespaceSource
application bindingshost-supplied data (mapping/object)
request.query.*request query-string parameters
request.path.*request path parameters
request.methodthe 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.

AttributeTypeDefaultMeaning
idstringrequiredUnique; binds to <response> and <include>.
modelstringrenderer defaultModel identifier passed to the provider.
conditionrule namenoneNamed rule gating execution; absent ⇒ always runs.
temperaturenumberprovider defaultGeneration parameter.
max_tokensintegerprovider defaultOutput token cap.
asyncyes|nonoPrompts run sequentially by default; yes opts this prompt into concurrent execution with other async prompts in its level.
cachedurationnoneTTL e.g. 24h; absent/0 ⇒ no caching.
toolsname listnoneComma-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.

AttributeTypeDefaultMeaning
idstringrequiredMust match a <prompt id="…">.
renderyes|noyesno ⇒ 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

htmlhtml
<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

htmlhtml
<param>product</param>   <!-- equivalent to: <fill>request.query.product</fill> -->

4.5 <include> — composition

htmlhtml
<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:

  1. Parse into a node tree; index <prompt>/<response>.
  2. Normalize the request — convert whatever request object the web framework hands over (FastAPI, Flask, Django, or a plain dict) into one uniform request namespace the template can read (request.query.*, request.path.*, request.method). This is what <param> and <fill>request.…</fill> resolve against.
  3. Evaluate conditions — run each prompt's named condition rule 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.
  4. Build the dependency graph from <include> references.
  5. Order execution by topological levels; a cycle is an error.
  6. 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).
  7. Cache (if cache set) keyed on resolved prompt text + model + temperature + max_tokens + sorted tools; a hit skips the model.
  8. Invoke the model via a provider-independent client with the resolved tools.
  9. 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 if render="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. <&lt;), 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 the request namespace, and support <param>;
  • evaluate condition as 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_tokens and resolved tools to the model client;
  • cache responses when a cache directive 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.
  • format on <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

customer.sprep.htmlhtml
<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.

SPREP Template Language Specification v0.1 · Reference implementation: HPRC Framework · © 2026 Rajesh Ramani · Apache-2.0