Skip to content
Prompting Press v0.2

FAQ

A typed, variant-aware prompt-template library — the prompt analogue of a typed config system. It turns typed inputs and a template into rendered text and content-addressed hashes, and nothing else. It works from Rust, Python, and TypeScript via one shared compiled Rust engine.

No. The library performs no I/O, makes no LLM calls, assembles no provider request body, counts no tokens, and parses no model output. It sits alongside any call layer (LangChain, OpenRouter, the direct Anthropic/OpenAI API, etc.).

No. There is no Registry surface. Prompt objects are constructed from YAML/JSON/TOML text (or from a shape dict/object) and held directly — in a module-level constant, a DI container, a config struct, or wherever fits the application. Versioning and history are owned by git.


The sound agreement check: a template’s referenced variable names must be a subset of the prompt’s declared variables map. If any referenced name is absent, construction fails with undefined_variable — never a silent empty render. This is a construction-time guarantee (not a CI-only optional).

Is the agreement check the same as check()?

Section titled “Is the agreement check the same as check()?”

No. They cover different things:

  • Agreement check — enforced at Prompt::new / from_yaml / from_json / from_toml. Catches template references ⊄ declared variables. Hard error; a Prompt that constructs is always agreement-sound.
  • check() — pure advisory lint on a constructed Prompt. The only live finding it can return is untrusted_without_guard: a trusted: false variable without a guard key. Advisory only — rendering still succeeds.

No. The trusted boolean is declarative metadata — the rendering kernel does not gate, block, or alter rendering based on it. A trusted: false field renders just like a trusted: true field in result.text. The flag drives two opt-in behaviors:

  1. check() advisory — prompts with trusted: false vars and no guard key produce an untrusted_without_guard finding.
  2. The opt-in guard — when GuardConfig { enabled: true } is passed to render, trusted: false variable values are wrapped in <untrusted>…</untrusted> delimiters in the rendered body, and RenderResult.guard carries an advisory instruction for the downstream model.

Neither of these is enforcement. Actual sanitization belongs in the application layer, before the vars are constructed.

No — and that is intentional. The type field on a PromptVariable (e.g. type: string) declares the variable’s logical type. The rendering kernel does not inspect it; runtime type enforcement is owned by the Vars model (garde / Pydantic / Zod). What type does is record the variable’s shape in the language-neutral prompt definition, so a typed Vars model can be generated from it (stringstr / string / String, integerint / number / i64, and so on) and then extended by hand. It is the one piece of per-variable metadata with a concrete consumer: the definition carries enough to scaffold the per-language Vars model, rather than each language re-declaring the field shape from scratch.

validation_required: true on a PromptVariable signals that the calling code must supply a validator covering that variable at construction time. In Python and TypeScript, construction raises/throws if a validation_required variable is not covered by the supplied validators class/schema. In Rust, coverage is structural — the generic V at the render<V>() call site must declare the field or the code won’t compile.


Every RenderResult carries two content-addressed SHA-256 hashes:

  • template_hashSHA256(resolved variant template source). The exact bytes the kernel renders against.
  • render_hashSHA256(rendered output text).

They are returned as data on the render result — nothing is sent anywhere (no telemetry, no OTel coupling). Storing them in a trace pins exactly which template produced which output. Because all three bindings share the same Rust engine, the hashes are byte-identical across Rust, Python, and TypeScript for the same inputs.

Is template_hash / render_hash the same as the per-variable trusted flag?

Section titled “Is template_hash / render_hash the same as the per-variable trusted flag?”

No. They are different concepts:

  • trusted — a per-variable input-trust flag (true = trusted, false = untrusted). Set in the prompt definition. Declarative metadata.
  • template_hash / render_hash — content-addressed fingerprints of the template source and the rendered output. Computed at render time and returned on the result.

No. When the guard is enabled, trusted: false variable values are wrapped in <untrusted>…</untrusted> delimiters in the rendered body (with &/</> entity-escaped), and RenderResult.guard carries an advisory instruction for the downstream model. This is delimiting, not sanitization — the library never inspects or rewrites the semantic content of a value. The result.text with guard-on differs from guard-off only in those delimiter wrappers.

How do I actually protect against prompt injection?

Section titled “How do I actually protect against prompt injection?”

The library’s scope is narrow. Prompt-injection protection is the application’s responsibility: apply sanitization in the application layer before building the vars, and route the guard text as a system-prompt addendum that instructs the downstream model. The library’s role is to delimit which fields are untrusted (via trusted: false) and surface the advisory — it does not provide the protection itself.


How is this different from Jinja (or minijinja)?

Section titled “How is this different from Jinja (or minijinja)?”

It isn’t a better template syntax — Prompting Press renders with minijinja under the hood, so the {{ }} templating will feel familiar. The difference is everything around the template that raw Jinja leaves you to build yourself:

  • Somewhere to keep prompts. Raw Jinja gives you a template string and nothing else — you still invent how prompts are named, loaded, typed, and versioned. Prompting Press is that structure: named, typed prompt definitions in YAML/JSON/TOML (or code).
  • A typed input contract with a build-time check. Jinja’s StrictUndefined errors only when a render actually hits a missing variable — at runtime, on that code path, if your data happens to exercise it. Prompting Press declares which variables a prompt has and checks that the template only uses those — at construction, with no data — so a template that references {{ customer }} when the prompt only declares user fails before you ship, not at 2am in production.
  • Variants. Named alternative versions of a prompt (formal/casual, v1/v2, per-language) that you select at render time instead of duplicating templates.
  • The same prompt in every language. One shared engine renders a prompt byte-identically from Rust, Python, and TypeScript — Jinja (Python) and nunjucks (JS) would drift.
  • A content hash of each render, so you can reproduce exactly what was sent.

How does this fit my framework’s system/user prompt split, or ChatPromptTemplate?

Section titled “How does this fit my framework’s system/user prompt split, or ChatPromptTemplate?”

Prompting Press stays out of the way of the split. It produces neutral role-tagged text — an ordered list of { role, text } messages, with role being system / user / assistant. Each framework then wants that shaped differently: LangChain keeps the system turn in the message list, Strands takes the system prompt as a separate argument, and CrewAI has no message list at all (it uses Agent/Task string fields). Rather than pick one framework’s convention, Prompting Press hands you the neutral messages and the per-framework Integrations pages show the few lines that reshape them — including why you should feed rendered text to the model directly instead of back through ChatPromptTemplate.


A fluent .chain() cannot cross the PyO3/napi FFI boundary (the bindings cannot return Self through FFI), and it would collide with Iterator::chain in Rust. The builder pattern is new() + append()append returns Result<(), ConsumerError> / void / raises, not Self, so it is intentionally not chainable.

Why does Composition use no guard expansion?

Section titled “Why does Composition use no guard expansion?”

Composition renders each entry against a default GuardConfig (opt-out). The guard text is per-render, not per-composition. To obtain the guard for entries in a multi-message sequence, render each entry individually with prompt.render(...) and route the guard text as a separate system message.

docs current as of 0.2.0