(From web research subagent, 2026-05-03 night spike. Triggered by observation that tainie has two prompt-templating
brittleness problems: server-side jinja in Qwen3.6 GGUF, and client-side "\n".join([...]) in prompts.py.)
t"..." returns a Template object (iterable of alternating str segments and Interpolation slots), **not a str
**. Each Interpolation carries value, expr (source text like "name"), conv, format_spec. Nothing renders
until a template function walks the parts and decides how to combine them.
The relevant property for prompts: structural separation between author-controlled literal text and runtime-controlled values. F-strings render eagerly and lose this distinction. A template function can apply per-slot policy — escape, fence, validate against a Pydantic model, refuse to render — before bytes reach the model.
awesome-t-strings lists ~12 libraries: SQL, HTML (tdom), regex, dedent, structured data. Zero are LLM-prompt
libraries.
- Pydantic AI's
TemplateStruses Jinja-style{{name}}, not PEP 750. - LangChain / LlamaIndex / Instructor / Mirascope — all f-string or Jinja.
- Anthropic / OpenAI SDKs — no t-string surface.
- HuggingFace
transformerschat templates — Jinja.
This is greenfield. If tainie ports prompts to t-strings, it's pioneering, not following.
Today: "\n".join(rule_strings). Each rule is a raw constant. The string {{name}} in NO_TEMPLATE_PLACEHOLDER_RULE
is a footgun — it could collide with downstream Jinja consumers.
A prompt() template function (~30 LOC) could:
- Accept
Template | strparts - Apply auto-escape on
Interpolation.value(e.g. fence dynamic content with code blocks) - Reject newlines or malformed values per-slot via
format_spec - Round-trip byte-identical to current output for a known input set
Splicing user-controlled paths, symbol names, or fixture content into examples is currently raw string concatenation. A
t"path: {p:safe}" slot could enforce shape via the format_spec.
Sketch: t"<system>...<example input={x}>...</example></system>" parses to a typed PromptNode tree. Diffable,
snapshot-testable, single render path emits OpenAI-chat / Anthropic-blocks / raw text. No such library exists yet.
Informal name candidates: tprompt, tdom-prompt.
- The Qwen3.5/3.6 jinja "No user query found" bug is server-side — inside the GGUF's bundled chat template. PEP 750
is client-side. The known fixes are: update LM Studio, switch to
lmstudio-communityGGUF, override prompt template in LM Studio settings. - vLLM / TGI / oMLX / LM Studio accept Jinja chat templates only. No inference server has a t-string contract.
- Pydantic AI's
TemplateStris Jinja-flavored. Replacing it means parallel adapter, not drop-in. - Type safety is modest. ty/mypy see
Template, not the slot types. PEP 750 didn't ship language hints; per-slot validation is still runtime.
Invest cautiously, scoped to client-side assembly, only after one concrete experiment succeeds. The first experiment:
- Port
src/tainie/agent/prompts.pyto a small in-packageprompt()template function (~30 LOC). - Accepts
Template | str, applies auto-escape onInterpolation.value. - Round-trips byte-identical with current output.
- Add snapshot tests proving equality with current string.
- Then add one new affordance — slot-level escaping for a user-controlled value (project path injected into a tool description). Demonstrate it rejecting a malformed input the current code would silently splice.
Evidence threshold to expand wider:
- (a) Preserves current model behavior on live regression suite
- (b) Catches at least one realistic prompt-injection or placeholder-collision case the f-string version misses
- (c) Reads more clearly to a reviewer
If yes: expand to tool docstrings and few-shots. If no: the win is cosmetic, not worth imposing the Python 3.14 floor on contributors.
The workspace neighbors (tdom, tdom-svcs) already commit to t-strings, so tainie is well-positioned to pioneer — but
pioneering is the right word.