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

# Imports and Fragments

> How agent documents compose: import resolution, field-by-field merge semantics, remove directives, the append directive, and fragment authoring conventions.

Agent documents compose through `imports:` — from local fragments under `.auto/fragments/` and from [managed templates](/reference/managed-templates). This page is the reference for that composition: how import paths resolve, the exact order and per-field semantics of the merge, the `remove:` and `append:` directives, and the pitfalls that follow from "later imports win".

## Import syntax

`imports:` (or the singular `import:`; `imports` wins when both are present) accepts a string or an array of non-empty strings:

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

Each entry is one of two path forms:

* **A relative path**, resolved against the importing file's directory. From `.auto/agents/pr-review.yaml`, the path `../fragments/environments/agent-runtime-base.yaml` resolves to `.auto/fragments/environments/agent-runtime-base.yaml`. Absolute paths and URLs are rejected: `Agent import must be a relative path`.
* **A managed-template specifier** — anything starting with `@`, in the form `@scope/name[@version|@latest]/subpath`. A file subpath is required (`Managed template import must include a file subpath`); omitting the version means `@latest`. See [Managed templates](/reference/managed-templates) for the full grammar and versioning behavior.

Two hard rules:

* An imported file must contain **exactly one** YAML document. (Files under `.auto/agents/` may hold several `---`-separated documents; imported fragments may not.)
* Import cycles fail with the full chain: `Agent import cycle detected: a -> b -> a`.

An unknown relative path, an unknown template, or a pin to a version the registry does not have all fail with `Agent import not found`.

<Note>
  An agent named `default` receives Auto's built-in Default base **before** its declared imports, which is why `name: default` alone is a complete, runnable agent. This built-in base is not an active managed template.
</Note>

## Merge order

Compilation of one agent document follows a fixed order:

<Steps>
  <Step title="Merge imports, in listed order">
    Starting from an empty draft, each import is compiled recursively (its own imports first, depth-first), then merged over the accumulated result. **Later imports win** over earlier ones, per the per-field semantics below.
  </Step>

  <Step title="Apply remove directives">
    The document's `remove:` directives run against the merged import result — so removals strip inherited items, and the document's own body can re-add.
  </Step>

  <Step title="Merge the document's own body last">
    Local fields always win over anything imported.
  </Step>
</Steps>

After the merge, the compiled result is validated as an agent. Three fields must be present after all merging: `name`, `harness`, and `environment` — imports typically supply the last two.

## Per-field merge semantics

"Later wins" means different things per field. The default rule — used by every field without special semantics — is: **records deep-merge key-wise; scalars and plain arrays are replaced wholesale**. On top of that default, four field families have their own behavior:

| Fields                                                                                                                                             | Family             | Merge behavior                                                                                                                                             | Removable via `remove:` |
| -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| `name`, `labels`, `annotations`                                                                                                                    | metadata           | default (records deep-merge, scalars override)                                                                                                             | no                      |
| `harness`, `model`, `reasoningEffort`, `displayTitle`, `session`, `spendCaps`, `concurrency`, `replace`, `manages`, `bindings`, `workingDirectory` | spec scalar        | default — so object-valued fields like `model`, `session`, and `bindings` deep-merge across imports; scalars override                                      | no                      |
| `systemPrompt`, `initialPrompt`, `onReplace`                                                                                                       | file-backed string | a plain string **replaces** the imported value; `{ append: … }` **concatenates** (see below)                                                               | no                      |
| `environment`, `identity`                                                                                                                          | inline resource    | inline objects deep-merge across imports; a string reference replaces an object and vice versa                                                             | no                      |
| `env`, `tools`                                                                                                                                     | named map          | per-key deep merge                                                                                                                                         | **yes**                 |
| `mounts`                                                                                                                                           | named array        | items merged by key: `name`, else `mountPath`; matching items deep-merge, new items append                                                                 | **no**                  |
| `triggers`                                                                                                                                         | named array        | items merged by key: `name`, else `event`, else the comma-joined `events` list, else `cron:<cron>:<timezone>`; matching items deep-merge, new items append | **yes**                 |

Consequences worth internalizing:

* **Deep-merging object fields compose across imports.** An imported fragment's `session: { archiveAfterInactive: … }` and your own `session: { observeSpawnedSessions: false }` merge into one policy; you do not lose the imported key by setting your own.
* **Named collections never lose items on override.** Overriding a trigger with the same key deep-merges your fields into the imported trigger; new keys append. To *drop* an inherited item, you need `remove:` — replacement of a whole collection never happens.
* **Trigger merge identity is fragile without names.** A trigger's merge key falls back to its event shape, so changing `events:` in an overlay creates a *second* trigger instead of overriding the imported one. Give triggers an explicit `name:` when a fragment is meant to be overridden — the name is authoring-only and is stripped before validation.

## `remove:` directives

`remove:` deletes named items inherited from imports. Exactly three targets are supported — `tools`, `triggers`, and `env` — anything else fails with `Unsupported agent remove target "<t>"; supported targets are tools, triggers, env`.

```yaml theme={null}
imports:
  - ../fragments/coding-base.yaml
remove:
  tools: github
  env:
    - DATADOG_API_KEY
    - NPM_TOKEN
```

* Each target takes a name or an array of names. Names key into: the tool alias for `tools`, the env var name for `env`, and the trigger merge key (name → event → joined events → `cron:<expr>:<tz>`) for `triggers`.
* Removals run **after** imports merge and **before** the document's own body merges, so a document can remove an inherited item and declare its own replacement in the same file.
* `mounts` are deliberately not removable — a fragment's mount grant is part of its contract.

## The append directive

On the three file-backed string fields — `systemPrompt`, `initialPrompt`, `onReplace` — a plain string replaces the imported value entirely. To *extend* the imported value instead, use a directive object with an `append:` key:

```yaml .auto/agents/pr-review.yaml (excerpt) theme={null}
imports:
  - "@auto/agents@latest/pr-review.yaml"
  - ../fragments/environments/agent-runtime-base.yaml
systemPrompt:
  append: |
    - **Render YAML and code as fenced, language-tagged blocks.** When you
      show an agent's `.auto/` YAML, a trigger snippet, or a command, put it
      in a fenced code block with the language tag.
variables:
  repoFullName: fractal-works/auto
  githubConnection: github-fractal-works
  customBrief: ""
```

The rules are deliberately minimal:

* **Exact whitespace, no hidden separator.** The resolved value is the imported string immediately followed by the appended text. Authors own paragraph breaks — start the appended block with a blank line when you want separation from the base. (The final schema still trims outer whitespace on prompt fields.)
* **`append` is the only operation.** The directive object must carry exactly one key with a string value; unknown operations and multiple keys fail at apply and dry-run, naming the field and file. A directive object on a spec scalar field (`model`, `displayTitle`, …) is rejected outright: `<field> does not support directive objects; append is supported on systemPrompt, initialPrompt, onReplace`.
* **Directives compose down the import chain.** Each document's append resolves against the value its imports produced, so a chain of fragments can each add a section. Apply-time `{{ $name }}` [variables](/reference/variables-and-templating) substitute inside appended text like any other imported string leaf.
* **Sibling composition works.** An overlay fragment that appends may be imported as a *sibling* of the fragment supplying the base — the pending append resolves when the parent merges the two, and chained pending appends fold in order. The one ordering mistake this allows is importing the overlay *before* its base; that fails explicitly: `append directive was merged before any base value; import the base document before its append overlay`.
* **An append with no base ever is an error.** If the whole import chain never produces a value for the field, the compile fails: `append directive has no imported systemPrompt to append to`.

The packaged PR Review role is a current consumer. Its `@auto/agents` entrypoint imports the shared runtime and operator baseline, then appends the role-specific review contract:

```yaml @auto/agents@latest/pr-review.yaml (excerpt) theme={null}
imports:
  - "@auto/fragments@latest/environments/codex-node24.yaml"
  - "@auto/fragments@latest/operator-baseline.yaml"
systemPrompt:
  append: |

    You are the code review agent for {{ $repoFullName }}.

    Review posture:
    - Prioritize correctness bugs, regressions, data integrity, operational
      risk, and missing tests over style nits.
```

## Authoring fragments

Fragments are reusable partial agent documents under `.auto/fragments/`. They are validated at apply time — single document, valid facade fields, no import cycles, supported removal targets — but produce no resources themselves; they only exist to be imported.

Conventions from production `.auto/` directories:

* Keep shared runtimes under `.auto/fragments/environments/`. A runtime fragment declares `harness` plus an inline `environment`:

```yaml .auto/fragments/environments/agent-runtime-base.yaml theme={null}
harness: claude-code
environment:
  name: agent-runtime
  labels:
    purpose: agents
  image:
    kind: preset
    name: node24
  resources:
    memoryMB: 8192
  steps:
    - RUN apt-get update && apt-get install -y --no-install-recommends postgresql-client redis-tools jq file && rm -rf /var/lib/apt/lists/*
    - RUN curl -fsSL https://temporal.download/cli.sh | sh && cp ~/.temporalio/bin/temporal /usr/local/bin/temporal
    - RUN npm install -g tsx
```

* Fragments may declare any facade field, and may have their own `imports:` — composition nests arbitrarily.
* A fragment's `variables:` map is ignored when the fragment is compiled as an import; the entry agent document owns the variable scope (see [Variables and templating](/reference/variables-and-templating#scope-and-precedence)).

### Inline resources deduplicate by content

An inline `environment:` (or `identity:`) compiles into a standalone generated resource named by its `name`. When several agent files generate a resource with the same name — the normal outcome of several agents importing the same runtime fragment — the definitions must be byte-identical, or apply fails:

```text theme={null}
Conflicting generated resource "environment/agent-runtime" from agent authoring:
defined differently in ".auto/agents/a.yaml" and ".auto/agents/b.yaml".
Inline generated resources must be identical when they share a name.
```

This is why a repo-local runtime fragment is the right pattern: every agent that imports it generates the *same* environment definition, and any divergence fails loudly instead of silently forking the runtime.

## Pitfalls

**Import order decides who wins.** Because later imports win, a local fragment that should override a managed template's field must come **after** the managed import. This repo's own `pr-review` agent documents the rule inline:

```yaml .auto/agents/pr-review.yaml (excerpt) theme={null}
imports:
  - "@auto/agents@latest/pr-review.yaml"
  # Later imports win the merge — this fragment must stay AFTER the managed
  # import so our tooled environment overrides the template's default.
  - ../fragments/environments/agent-runtime-base.yaml
```

Swap the two lines and the template's default environment silently wins instead.

**Unknown root-level keys are silently ignored.** The compiler only routes keys it knows; a typo'd field name does not fail apply. Strict unknown-key rejection applies *inside* structured fields (`model`, `session`, `triggers`, …) at final validation. See [Agent file](/reference/agent-file) for the full key list.

**Legacy layouts are rejected, not ignored.** `kind:`/`metadata:`/`spec:` envelopes inside agent files, and standalone `.auto/sessions/`, `.auto/environments/`, `.auto/identities/` directories, all fail apply with migration guidance — environments and identities are inline-only.

**`remove:` cannot touch mounts.** Use an overlay that overrides the mount's capabilities instead, or author a variant fragment without the mount.
