Skip to content

Include syntax and settings

An include is a tag in a node's text that is replaced by another node's text when the node is read. For what that means and why, see How includes work.

Status words on this page: built means merged on main and described from the code and the product specs; staged means accepted but with a different effect than the final one; not built means do not rely on it. Merged through hadron-server#2065 (grammar, write refusals) and hadron-server#2118 (the read side), and hadron-server#2226 (the writer's read check).

Grammar

INCLUDE  = "{{>" [space] TARGET [space SETTING]... [space] "}}"
TARGET   = NODE-URN | LOC
NODE-URN = hrn:node:<root>:<memory>:<loc>
LOC      = a loc in the includer's own memory, no whitespace
SETTING  = "rev=" N | "approved" | "signed"
N        = a positive integer
  • A target that starts with hrn:node: is a node URN in the flat form, and it always names that exact node, in any memory the reader can read. Any other target is a loc, and it names only a node in the includer's own memory.
  • Write the flat form only. The product specification does not accept the older form (hrn:node:<root>::<memory>::<loc>); whether a write refuses it today is not something this page claims.
  • Settings go in any order and each appears at most once.
{{> policies:conduct}}
{{> hrn:node:acme.com:policies:conduct rev=3}}

Settings

With no setting an include serves the target's latest revision.

Setting Unpinned With rev=N State
none the latest revision revision N built
approved the newest approved revision revision N, which must be approved not built: renders the notice
signed the newest signed revision revision N, which must be signed staged: stored and ignored until signing ships, served as if absent
approved signed newest revision both approved and signed revision N, both staged: behaves as approved, so renders the notice

How rev=N resolves: a stored snapshot of revision N is served when one exists. When none exists and N is the target's current revision, the current text is served. A snapshot captured while the node lived in another memory is never served, and does not fall back to the current text. A revision that cannot be served is a broken include.

approved does not start working when a helper merges. It needs the renderer wired to it and merged integration evidence, and this page will change only then.

Which reads resolve

Read Resolves
GraphQL Node.content (node, nextNode, findNodes, edge ends, mutation payloads) yes; raw: true returns the stored text
GraphQL nodeBatch, bare content no, stored text; content(raw: false) resolves
MCP hadron_get_node, hadron_get_nodes, hadron_get_next_node yes; raw: true returns the stored text
Skill export (skillPlan), nodeExport(full: false), PDF and Google Doc export yes
nodeExport(full: true), memory export no, stored text
A past revision (NodeRevision.content) no, stored text
Search matching, the index and embeddings built from the stored text, never resolved
content returned on a search hit as any Node.content: resolved by default, stored with raw: true
Run, chat and session renders yes

All resolving reads use the reader's own access. The command-line behaviour is documented separately once the CLI release that states it explicitly lands.

Broken includes

An include that cannot be shown renders [Include unavailable], identical for a missing target, a deleted one, one the reader may not read, a revision that cannot be served, a chain deeper than 10, and a cycle. The rest of the node resolves. Nesting counts from the includer: its own tags are level 1, ten nested levels resolve, an eleventh is broken. A cycle is caught at its first repeat.

Currently (code behaviour, not a platform promise): a target in an encrypted memory with no key available shows the same notice, and chat-message and record nodes are never resolved.

What a write does today

Tag written Today
Malformed tag refused, INVALID_INCLUDE
Node that would include itself, directly or through other includes, by a tag the write adds or changes refused, INCLUDE_CYCLE
A chain deeper than 10 saves; reads show the notice past level 10
Added or changed tag whose target is missing, deleted or unreadable to the writer refused, INCLUDE_TARGET_UNAVAILABLE (one error for all three; names only the authored tag)
Tag already stored and unchanged, target now unreadable no access recheck; saves, keeps its edge, the notice shows on read
rev=N the target does not have accepted; the notice shows on read

The node gets a derived edge of governed kind INCLUDE to each distinct target that currently exists and is readable by the writer. A stored tag whose target has since become unreadable keeps the edge it had and never gains a new one. Only the node's content owns these edges: editing or deleting that edge directly is refused with INCLUDE_EDGE_DERIVED.

Added or changed is decided against the node's stored text: kind, target and settings are compared, so spacing inside the braces is not a change but a settings change, or a different form of the tag for the same node, is. The check is existence plus read access to the target's memory; making that decision never decrypts the target. The check on added or changed tags applies to GraphQL and MCP writes alike (create, update, batch replace, merging one node into another with mergeNodes, restore a revision, search-and-replace). Git sync and the controller's root-node writes skip the access check and run the syntax and cycle checks only. Restoring a published Agent snapshot runs no include check at all; it only re-derives the edges.

Every save still parses the submitted text, so a malformed tag is refused whether or not it is new. The cycle check starts from the tags the write adds or changes. A cycle that formed later through tags you did not touch does not block an unrelated save.

A memory merge (mergeMemories) is never refused over include tags; reads conceal what a reader cannot see.

Not built: copying a node or memory (clone, extract) does not yet run this check on the copied tags (hadron-server#2167).

Known gap

A tag inside a code span or fenced block is kept literal by the parser, but the template step still drops it on read, so an example of the syntax disappears when the node is read. See How includes work.