Mustache template syntax¶
Node content can include Mustache template variables. When an agent reads a
node, Hadron compiles the content by resolving variables from the memory's
data. This applies to any node type — most often system nodes (prompts,
conversation stages) and info nodes (knowledge content templated per user).
Variable syntax¶
Variables use double braces. Names follow JavaScript identifier rules (letters, numbers, underscores, dots for nested access). Whitespace inside the braces is ignored.
To access a nested field:
To pull data from a different node by URN, use a colon-separated path:
settings here is a node URN segment under the same memory. The compiler
treats anything before the first dot as a node URN; everything after the
dot is field access on that node's data.
Resolution order¶
When the compiler encounters {{name}}, it tries these sources in order
and returns the first match:
- The node's own
datafield. If the node carriesdata: { name: "Alex" }, that wins. - The memory's default data node at the memory root. By convention
each memory has a
datanode (created on first write); any field on that node is available as a bare{{field}}. - Cross-node lookup for dotted references like
{{settings.theme}}. The compiler reads the node at URNsettingsand pullsthemefrom itsdata.
Stage prompts in chatbot agents follow the same rules but also see the chat's per-turn context (the user's extracted data, the chat's running state).
Defaults and missing variables¶
If a variable is missing in every source, the compiler:
- Drops the variable silently —
{{missing}}becomes the empty string. No error, noundefinedtext leaking through to the LLM. - Logs a debug-level warning so you can spot template typos in the server logs without it affecting the agent's response.
To provide a default at the call site, use a fallback section:
{{#name}}…{{/name}} renders only if name is set; {{^name}}…{{/name}}
renders only if it isn't. This is the standard Mustache "section" /
"inverted section" pattern.
Escaping¶
Variables in {{ }} are HTML-escaped by default. To insert raw text
(typically only useful in markdown contexts where HTML escaping would
mangle the output), use triple braces:
Use this with care — raw insertion bypasses any sanitization, so only do it for content you control.
Partials¶
Includes use the {{> }} syntax. The path resolves to a node URN inside
the same memory:
Hadron reads the referenced node, compiles it (recursively), and inlines the result. Use partials for prompt fragments shared across stages (metadata specs, safety language, tone guidelines) so a one-line edit updates everywhere.
Worked example¶
A stage prompt:
You are advising {{memory.name}} ({{memory.business_type}}, {{memory.team_size}}).
Their goal: {{memory.top_goal}}.
Their challenge: {{memory.biggest_challenge}}.
{{> prompts:partials:metadata-spec}}
With the user's memory containing:
{
"memory.name": "Alex Chen",
"memory.business_type": "specialty coffee shop",
"memory.team_size": "3 (1 owner + 2 part-time)",
"memory.top_goal": "break even consistently",
"memory.biggest_challenge": "winter foot traffic drop"
}
…and prompts:partials:metadata-spec containing the standard "respond via
the respond tool" instructions, the compiled prompt sent to the LLM has
the user's profile inlined and the metadata spec appended.
Reading raw vs. compiled¶
Tools and APIs that read nodes return compiled content by default. To see
the raw template (e.g. while debugging a prompt), pass raw: true:
- MCP:
h-read-nodewithraw: true - Portal: the node detail page has a "Raw" toggle on the content panel
- GraphQL:
node(rel: "...", raw: true)
Related¶
- Building an agent — where templates first appear in the setup flow.
- Building a chatbot agent — uses templates and partials throughout.
- Node types —
systemnodes (prompts, partials) are the most common template carriers.