Recursive AI

Templating

All onellm docs

OneLlm.Templating renders prompts written in the language of Go's text/template package. It is a standalone engine: it knows nothing about agents or providers, takes a tree of plain values as its scope, and is used by definitions, scripted runs and deciders. Parsed templates are cached and immutable, so rendering is thread-safe.

C#
string s = Template.Render("Hello {{ .inputs.name | upper }}",
    new Dictionary<string, object?> { ["inputs"] = new Dictionary<string, object?> { ["name"] = "ada" } });
// "Hello ADA"

The language

The syntax and semantics are those of the Go package documentation (<https://pkg.go.dev/text/template>). Where that page leaves output open, OneLLM matches what Go prints; the behaviour was settled by running Go 1.26 against the same templates, and the cases are kept in testdata/templating/ and replayed by the tests.

  • Text and actions: {{ ... }}, comments {{/* ... */}}, trim markers {{- ... -}} (a marker needs a space beside it).
  • Pipelines and variables: a | f b, $x := ..., $x = ..., $ for the scope root, parenthesised pipelines, field chains (.a.b), index by key.
  • Control: if / else if / else, range (with else, break, continue, one- or two-variable form), with / else with, define, template, block.
  • Range works over lists and arrays, maps (keys in sorted order), and integers. A string is not rangeable.
  • Builtins: and and or (short-circuit), not, len, index, slice, print, printf, println, eq (several arguments), ne, lt, le, gt, ge, call, html, js, urlquery.
  • Values: nil or a missing key prints <no value>. Integers are Go int, other numbers are float64 (printed as Go prints %v: shortest form, with an exponent below 1e-4 and from 1e+06 upward (observed from Go 1.26); JSON text switches at 1e-6 and 1e21). Booleans print true/false. Lists and maps print as [a b] and map[k:v] with keys sorted.
  • Scope values: null, strings, booleans, numbers, Dictionary<string, object?> and other string-keyed maps, List<T>, arrays and JsonElement. They are read directly, without reflection.

Known differences from Go

  • date takes RFC 3339 times only (Z or +hh:mm, one- or two-digit hour).
  • Strings are UTF-16. slice on a string counts UTF-8 bytes, but a cut through the middle of a multi-byte character yields U+FFFD instead of a raw byte, so the length can differ.
  • slice of a list cannot extend past the list's length; Go allows it up to the backing array's capacity, which a host list does not have.
  • Complex number constants (1i) are not supported.
  • %v of a 32-bit float is not modelled (all non-integer numbers are 64-bit).
  • Decimal values are converted to float64.
  • Regular expressions use a linear-time engine, so back-references and look-around are not available.

Standard functions

Always available, on top of the builtins:

FunctionMeaning
raw sReturns s unchanged. Use {{ raw "{{" }} to print a literal delimiter outside a code region.
embed refInserts another prompt, rendered in the same scope (see Embed).
base p, dir p, ext p, trimExt p, pathJoin a b ..., isAbs pSlash-path helpers (/ separated; pathJoin cleans the result).
lower s, upper s, trim sCase mapping and whitespace trimming.
trimPrefix s p, trimSuffix s p, replace s old newreplace replaces every occurrence.
contains s sub, hasPrefix s p, hasSuffix s pSubstring tests.
default fallback vfallback when v is nil or the empty string, otherwise v.

OneLLM-only extensions

These do not exist in Go's package or in any prompt host that only offers the functions above. A template that uses them is not portable.

FunctionMeaning
toJson v, toPrettyJson vJSON text of a value. Keys sorted, no HTML escaping; pretty form indents by two spaces.
toYaml vYAML text of a value (via OneYaml).
join sep listJoins the items of a list with sep.
split sep sSplits s on sep into a list.
indent n s, nindent n sPrefix each line with n spaces; nindent also starts with a newline.
truncate n sAt most n characters of s.
nowThe current local time from the render's TimeProvider. Rendering fails if none was given.
date layout tFormats an RFC 3339 string or time with a Go layout (2006-01-02).
regexMatch pattern s, regexReplace pattern repl sRegular expressions ($1, ${name} in the replacement).
dict k v ..., list a b ...Build a map or a list inline.

Hosts can register their own functions with TemplateFunctions (delegates with declared argument kinds; no reflection). A host function can replace a standard one or a builtin by name.

Scope

The host supplies the scope. The names OneLLM definitions use: .inputs, .system, .time (now, today, timezone), .git (opt-in), .env (empty unless the host allow-lists names), .swarm, .invocationMode, and .turns[i].text / .turns[i].json in scripted runs. The engine itself only needs a map.

Code regions

Prompts often show template syntax or code. Before parsing, {{ and }} inside Markdown code are escaped so they print literally: fenced blocks (three or more backticks or tildes, indented at most three spaces, closed by a fence at least as long) and inline spans (a run of backticks closed by a run of the same length). Text outside code regions is parsed normally. Pass EscapeCodeRegions = false in TemplateParseOptions to turn this off. Error positions refer to the original text.

Embed

{{ embed "ref" }} inserts the rendered text of another prompt, in the same scope, at that point.

  • Sources, in order: the first IEmbedSource that returns content wins. Put the relative-path source first (FileEmbedSource: relative to the including file, confined to a root, symbolic links resolved, absolute references ignored), then registered prompts (PromptEmbedSource), then any remote source such as an HTTP resolver. No source is configured by default, so a template cannot read files or the network unless the host allows it.
  • Depth and cycles: nesting is limited (MaxEmbedDepth, default 16) and a prompt that includes itself, directly or through others, fails with the chain of keys. Both errors carry the position of the embed call.
  • Embedded text is parsed and cached like any template.

missingKey

MissingKeyMode.Default renders a missing map key as <no value>. MissingKeyMode.Error fails and names the key. Definitions default to Default; deciders always use Error.

Errors

TemplateException carries Kind (Parse, Execute, Embed, Limit), the template name, 1-based Line and Column, byte Offset and the detail. Rendering is also limited to 64 Mi characters of output and honours a cancellation token.

Prompt caching

Provider prompt caches match on the exact leading text. A system prompt that renders now, date of now, .time.now or anything else that changes between calls differs every time and defeats the cache. Put changing values late in the prompt (a user turn, or the end of the system prompt), or use .time.today when day resolution is enough.