> ## Documentation Index
> Fetch the complete documentation index at: https://docs.auto.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Variables and Templating

> Apply-time variables, runtime event templates, and mount ref templates in .auto/ files — plus GitHub Sync context variables and file-backed prompt strings.

`.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

| Token                    | Example                                 | Resolved                                       | Rendered against                                                                                               |
| ------------------------ | --------------------------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `{{ $name }}`            | `{{ $repoFullName }}`                   | At apply time, during compilation              | The importing agent's `variables:` map (plus Sync-injected context variables)                                  |
| `{{ dot.path }}`         | `{{github.pullRequest.number}}`         | At runtime, when a trigger fires               | The normalized event payload — **no `payload.` prefix**                                                        |
| `{{ payload.dot.path }}` | `{{payload.github.pullRequest.number}}` | At mount staging, when the sandbox is prepared | The session's run-input wrapper `{ triggerEventId, payload }` — the `payload.` prefix is correct **only here** |

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](/reference/managed-templates) alike — so a parameterized template is written once and each consumer supplies values instead of overriding fields one by one.

```yaml .auto/agents/pr-review.yaml theme={null}
name: pr-review
imports:
  - "@auto/agents@latest/pr-review.yaml"
  - ../fragments/environments/agent-runtime-base.yaml
variables:
  repoFullName: fractal-works/auto
  githubConnection: github-fractal-works
```

Inside the imported `@auto/pr-review` fragment, those variables parameterize the trigger connection, the `where` filter, and the git mount:

```yaml packages/@auto/pr-review/fragments/pr-review.yaml (excerpt) theme={null}
mounts:
  - kind: git
    repository: "{{ $repoFullName }}"
    mountPath: /workspace/auto
    ref: refs/pull/{{payload.github.pullRequest.number}}/head
triggers:
  - name: pr-review
    events:
      - github.pull_request.opened
      - github.pull_request.reopened
      - github.pull_request.synchronize
    connection: "{{ $githubConnection }}"
    where:
      $.github.repository.fullName: "{{ $repoFullName }}"
```

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](/reference/imports-and-fragments) — 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:

```text theme={null}
Unresolved variable "{{ $repo }}" in imported ../fragments/base.yaml:
declare it in the importing agent's variables (available: {{ $repoFullName }}).
```

<Note>
  Variables substitute into the imported YAML documents themselves. Content pulled in through a `file:` reference (see [File-backed strings](#file-backed-strings)) is read verbatim and is **not** substituted.
</Note>

## Context variables injected by GitHub Sync

When [GitHub Sync](/concepts/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:

| Variable           | Value                                                                  |
| ------------------ | ---------------------------------------------------------------------- |
| `repoFullName`     | The binding's repository in `owner/name` form                          |
| `githubConnection` | The name of the GitHub connection backing the binding's provider grant |

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`).

```yaml theme={null}
displayTitle: "Review PR #{{github.pullRequest.number}}: {{github.pullRequest.title}}"
initialPrompt: |
  Review GitHub pull request #{{github.pullRequest.number}} in {{github.repository.fullName}}.
```

### 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](/reference/events/github).
* Tokens must **not** carry a `payload.` prefix. The apply rejects them at authoring time:

```text theme={null}
Template renders against the event payload; drop the "payload." prefix
(use "{{github.…}}", not "{{payload.github.…}}").
Offending tokens: {{payload.github.pullRequest.number}}
```

### Top-level namespaces per event source

The available template namespaces are the top-level keys of each event source's normalized payload:

| Event source                                                                         | Namespaces                      | Examples                                                                               |
| ------------------------------------------------------------------------------------ | ------------------------------- | -------------------------------------------------------------------------------------- |
| [GitHub](/reference/events/github) (`github.*` events)                               | `github.*`                      | `{{github.pullRequest.number}}`, `{{github.repository.fullName}}`, `{{github.action}}` |
| [Chat](/reference/events/slack) (`chat.message.*` events — Slack, Discord, Telegram) | `chat.*`, `message.*`, `auto.*` | `{{message.text}}`, `{{chat.channelId}}`, `{{chat.threadId}}`, `{{chat.provider}}`     |
| [Linear](/reference/events/linear) (`linear.issue.*` events)                         | `linear.*`                      | `{{linear.issue.identifier}}`, `{{linear.issue.title}}`, `{{linear.issue.url}}`        |
| [Heartbeat](/reference/events/cron-and-webhooks) (cron triggers)                     | `heartbeat.*`                   | `{{heartbeat.scheduledAt}}`, `{{heartbeat.scheduleId}}`                                |
| [Custom webhooks](/reference/events/cron-and-webhooks)                               | The raw request body            | `{{order.id}}` for a body of `{"order": {"id": …}}`                                    |

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:

```yaml theme={null}
mounts:
  - kind: git
    repository: "{{ $repoFullName }}"
    mountPath: /workspace/auto
    ref: refs/pull/{{payload.github.pullRequest.number}}/head
    depth: 1
```

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:

```text theme={null}
Agent git mount ref depends on event payload path
"payload.github.pullRequest.number" requires session input
(ref: refs/pull/{{payload.github.pullRequest.number}}/head).
This agent must be started by a matching event trigger that supplies that path …
```

See [Mounts](/reference/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:

```yaml .auto/agents/staff-engineer.yaml theme={null}
name: staff-engineer
imports:
  - ../fragments/environments/agent-runtime-base.yaml
systemPrompt:
  file: ./prompts/staff-engineer.md
```

* 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](/reference/imports-and-fragments#the-append-directive).
