Skip to content

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

Welcome, {{name}}! You live in {{location}}.

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:

Welcome, {{user.name}}! Your team is {{user.team.name}}.

To pull data from a different node by URN, use a colon-separated path:

Theme: {{settings.theme}}

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:

  1. The node's own data field. If the node carries data: { name: "Alex" }, that wins.
  2. The memory's default data node at the memory root. By convention each memory has a data node (created on first write); any field on that node is available as a bare {{field}}.
  3. Cross-node lookup for dotted references like {{settings.theme}}. The compiler reads the node at URN settings and pulls theme from its data.

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, no undefined text 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:

Welcome, {{#name}}{{name}}{{/name}}{{^name}}friend{{/name}}!

{{#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:

{{{markdown_block}}}

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:

{{> prompts:partials:metadata-spec}}

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-node with raw: true
  • Portal: the node detail page has a "Raw" toggle on the content panel
  • GraphQL: node(rel: "...", raw: true)