Skip to main content
.auto/ files carry three distinct {{ … }} token grammars, and they render at different times against different data. This page defines each grammar, which fields it applies to, and how values are resolved — including the context variables GitHub Sync injects automatically and the file: syntax for keeping long prompts in neighboring files.

The three grammars at a glance

The leading $ is the deliberate non-collision guarantee between the first two grammars: the apply-time substitutor matches only {{ $name }} tokens and leaves every $-free {{ … }} token untouched, so runtime tokens inside imported templates pass through compilation verbatim. There is no escape syntax and none is needed — a literal shell $ ($HOME) lacks the braces, and a runtime token lacks the inner $.

Declared variables

An agent document may declare a top-level variables: map. Each value substitutes into {{ $name }} tokens found in the content the document imports — fragments and managed templates alike — so a parameterized template is written once and each consumer supplies values instead of overriding fields one by one.
.auto/agents/pr-review.yaml
Inside the imported @auto/pr-review fragment, those variables parameterize the trigger connection, the where filter, and the git mount:
packages/@auto/pr-review/fragments/pr-review.yaml (excerpt)
Note how {{ $repoFullName }} and {{payload.github.pullRequest.number}} coexist in the same file: the first resolves at apply time, the second at mount staging.

Declaration rules

  • variables: is a map of name → value. Names must match [A-Za-z_][A-Za-z0-9_]*.
  • Values must be strings, numbers, or booleans; numbers and booleans are coerced to their string form, so a YAML number is usable without quoting. Any other value type fails apply, naming the variable.
  • A {{ $name }} reference tolerates inner whitespace: {{ $repo }} and {{$repo}} are equivalent.

Scope and precedence

  • Variables are declared on the entry agent document — the file under .auto/agents/. The scope flows down the entire import tree transitively; a fragment compiled as an import inherits the entry document’s scope, and any variables: map the fragment declares itself is ignored during that compile.
  • Substitution applies to imported content only: the string leaves of every imported document, before that document is parsed and merged. The declaring document’s own body is never substituted.
  • Because substitution happens before the merge, the importer’s concrete fields still override substituted defaults through the normal merge semantics — declaring a variable and overriding a field are independent mechanisms.
  • With an empty scope (no declared variables and no context variables), imported content passes through byte-identical — every {{ … }} token, $-led or not, is untouched.
  • With a non-empty scope, every {{ $name }} token in imported content must resolve. An unresolved reference fails the apply with an error naming the variable, the importing specifier, and the available names:
Variables substitute into the imported YAML documents themselves. Content pulled in through a file: reference (see File-backed strings) is read verbatim and is not substituted.

Context variables injected by GitHub Sync

When GitHub Sync applies a .auto/ directory, it injects two variables derived from the repo binding, so a repo authored with template parameters — for example a forked template repo — applies cleanly without any per-tenant edits: Context variables merge beneath each entry document’s declared variables: map — a declared value always wins. The pr-review.yaml example above declares both explicitly, which pins the agent to fractal-works/auto no matter which apply surface runs; deleting the variables: block would leave GitHub Sync to fill both in from the binding. There is no context variable for other providers. Templates that need a Slack connection use the conventional literal connection name slack instead of a variable.

Dry-run parity

The auto.resources.dry_run MCP tool mirrors the same two defaults so a bundle that would apply under Sync does not fail its dry-run:
  • With exactly one enabled GitHub Sync binding on the project, both variables resolve from that binding, exactly as Sync would resolve them.
  • With zero or multiple bindings, repoFullName cannot be defaulted; githubConnection falls back to the org’s GitHub connection only when the org has exactly one.
  • When a referenced variable cannot be defaulted, the dry-run fails with a message naming the variable, why it could not be defaulted (no candidate, or multiple candidates — listed), and the fix: declare it in variables:, or reduce to a single binding/connection.

Runtime event templates

Three authoring fields render {{ dot.path }} tokens at runtime, against the normalized payload of the event that reached the session:
  • triggers[].message — the text delivered into a session when the trigger routes an event to it.
  • initialPrompt — the kickoff prompt of a trigger-spawned session.
  • displayTitle — the session’s title (unless set to the literal infer).

Rendering rules

  • The renderer replaces each {{ path }} token with the value at that dot-path in the event payload. Strings, numbers, and booleans render as their string form; objects and arrays are JSON-stringified.
  • A missing or null path renders as an empty string — it does not error. A typo’d path silently delivers blank text, so double-check paths against the event catalog.
  • Tokens must not carry a payload. prefix. The apply rejects them at authoring time:

Top-level namespaces per event source

The available template namespaces are the top-level keys of each event source’s normalized payload: Each event catalog page documents the full payload shape per event.

Mount ref templates

Git mount ref values are the one surface where the payload. prefix is correct. They render at mount staging against the session’s persisted run input — the wrapper { triggerEventId, payload } that trigger-spawned sessions store:
Each token must resolve to a scalar (string, number, or boolean). Manual and agent-started sessions carry no event payload, so a payload-dependent ref cannot resolve for them — auto refuses at spawn time rather than failing later during environment preparation:
See Mounts for the full mount schema.

File-backed strings

The three long-prompt fields — systemPrompt, initialPrompt, and onReplace — accept a file: reference in place of an inline string, so large prompt text can live in a neighboring file:
.auto/agents/staff-engineer.yaml
  • The path is resolved relative to the declaring file’s directory. Absolute paths and URLs are rejected (systemPrompt.file must be a relative path); a missing file fails apply naming the field and path.
  • The file’s bytes are read as UTF-8 and become the field’s value before any merging happens — later phases of compilation see only strings.
  • The compiled value is what the schema validates and what renders at runtime, so an initialPrompt file may carry runtime {{github.…}} tokens (and the payload. prefix guard applies to it too).
  • Apply-time {{ $name }} variables are not substituted into the referenced file’s contents — only into imported YAML documents.
These same three fields also support the { append: … } composition directive for extending an imported prompt instead of replacing it. That directive is part of the merge language — see Imports and fragments.