Skip to main content
Agent documents compose through imports: — from local fragments under .auto/fragments/ and from 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:
.auto/agents/pr-review.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 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.
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.

Merge order

Compilation of one agent document follows a fixed order:
1

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

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

Merge the document's own body last

Local fields always win over anything imported.
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: 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.
  • 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:
.auto/agents/pr-review.yaml (excerpt)
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 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:
@auto/agents@latest/pr-review.yaml (excerpt)

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:
.auto/fragments/environments/agent-runtime-base.yaml
  • 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).

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:
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:
.auto/agents/pr-review.yaml (excerpt)
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 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.