---
slug: agents/aetherfy-yaml
title: aetherfy.yaml reference
kind: reference
surface: agents
summary: Complete field reference for the Aetherfy agent configuration file aetherfy.yaml — every field with its type, default and allowed values, the RFC 7396 merge-patch semantics, archive and lockfile requirements, and the silently-ignored-unknown-keys hazard.
sources:
  - aetherfy-control-plane:shared/config_parser.py
  - aetherfy-control-plane:api/routes/agents.py
  - aetherfy-control-plane:models/agent.py
  - aetherfy-cli:cmd/deploy.go
---

# aetherfy.yaml

## What aetherfy.yaml is on Aetherfy

`aetherfy.yaml` is the single declarative file that describes an Aetherfy agent.
It is **required** — a deploy without it fails. It must sit at the root of the
archive you upload, or at the root of the build context when Aetherfy builds
from a linked GitHub repository.

Aetherfy accepts four filenames, and uses the first one it finds in this
priority order:

| Priority | Filename |
|---|---|
| 1 | `aetherfy.yaml` |
| 2 | `aetherfy.yml` |
| 3 | `.aetherfy.yaml` |
| 4 | `.aetherfy.yml` |

A minimal valid file for Aetherfy is two fields:

```yaml
name: hello-agent
runtime: python3.12
```

A fuller example using most of the surface:

```yaml
name: nightly-report
runtime: python3.12
type: job
description: Rolls up yesterday's events and writes a summary row.
workspace: reporting
database_collection: report-embeddings
entrypoint: main.py
memory_mb: 512
idle_timeout_minutes: 5
keep_alive: false
schedule: "0 3 * * *"
regions:
  - us-east-1
github_dependencies:
  - myorg/shared-lib@v1.2.0
```

## Every field Aetherfy accepts in aetherfy.yaml

| Field | Type | Required | Default | Allowed values | Notes |
|---|---|---|---|---|---|
| `name` | string | **yes** | — | any string | **This is how a deploy picks its target agent.** `afy deploy` uses `--agent` when you pass it and otherwise the `name` here; with neither it fails with `Agent name not found. Use --agent flag or set 'name' in aetherfy.yaml`. What `name` does *not* do is merge-patch onto an existing agent's record — editing it retargets the deploy rather than renaming an agent, and renaming is `afy agents rename`. |
| `runtime` | string | **yes** | — | `python3.11`, `python3.12`, `python3.13`, `node20`, `node22`, `node20-ts`, `node22-ts`, `bun`, `dockerfile` | **Immutable after the agent exists.** A change is rejected with `RUNTIME_IMMUTABLE` (HTTP 422). |
| `type` | string | no | `service` | `service`, `job` | Lowercased before validation. |
| `description` | string or null | no | `null` | maxLength 1000 | Control characters are stripped; if nothing remains, the value becomes `null`. |
| `tier` | string or null | no | `free` | `free`, `starter`, `performance`, `enterprise` | Validated and then **discarded** — your account's plan is the real source. Accepted but ignored. |
| `workspace` | string or null | no | `null` | a non-blank string, or `null` to clear | Empty or whitespace-only is rejected. |
| `database_collection` | string or null | no | `null` | a non-blank string | A default-collection hint, not an access rule. The collection must already exist at deploy time. |
| `spawn` | object or null | no | `null` | see below | Configures worker spawning. |
| `spawn.enabled` | boolean | no | `false` | `true`, `false` | |
| `spawn.workers` | array of strings or null | no | `null` | agent names | Each named agent must exist and be a live `type: job` agent at deploy time. |
| `regions` | array of strings or null | no | `null` | `us-east-1`, `eu-central-1`, `ap-southeast-1` | Not stored on the agent — consumed per deployment. When omitted, Aetherfy picks defaults bounded by your plan. |
| `memory_mb` | integer or null | no | `256` | `256`, `512`, `1024`, `2048`, `8192` | An explicit `null` is **rejected**. Also bounded by your plan's maximum memory. |
| `idle_timeout_minutes` | integer or null | no | `5` | a positive integer | An explicit `null` is **rejected**. Two layers, two rules: the `aetherfy.yaml` parser and the deploy path enforce **no** upper bound, while the per-tier maximum (Free 5, Starter 15, Performance 30, Enterprise unlimited minutes) is enforced when an agent is created or updated through the Aetherfy API or dashboard. See [/platform/limits](/platform/limits). |
| `keep_alive` | boolean or null | no | `false` | `true`, `false` | An explicit `null` is **rejected**. Requires a plan that allows always-on agents. |
| `entrypoint` | string or null | no | runtime default | a filename present in your archive | The file must exist or the build fails. Defaults are tabulated below. |
| `schedule` | string or null | no | `null` | a 5-field UTC schedule expression | Only valid on `type: job` agents. See [/agents/scheduled-tasks](/agents/scheduled-tasks). |
| `github_dependencies` | array of strings | no | `[]` | `owner/repo@ref` | Must fullmatch `[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+@[A-Za-z0-9._/-]+`. Private repositories require the Aetherfy GitHub App to be connected. |

## The runtime field is immutable on Aetherfy

`runtime` is the one field an existing Aetherfy agent will not let you change.
Deploying a different runtime to an agent that already exists is rejected with
`RUNTIME_IMMUTABLE` and HTTP 422. To move to a different runtime, delete the
agent and create it again under the same name.

Aetherfy accepts exactly nine runtime values:

| Runtime | Family |
|---|---|
| `python3.11` | Python |
| `python3.12` | Python |
| `python3.13` | Python |
| `node20` | Node |
| `node22` | Node |
| `node20-ts` | Node with TypeScript |
| `node22-ts` | Node with TypeScript |
| `bun` | Bun |
| `dockerfile` | Your own container build |

`python` alone is **not** a valid runtime on Aetherfy. Documentation published
previously showed `runtime: python`, and that value fails the deploy. Always
write the full version, such as `runtime: python3.12`.

## Entrypoint defaults on Aetherfy

If you omit `entrypoint`, Aetherfy uses the default for your runtime. The file
must exist in the code you upload, or the build fails.

| Runtime | Default entrypoint |
|---|---|
| `python3.11`, `python3.12`, `python3.13` | `main.py` |
| `node20`, `node22` | `index.js` |
| `node20-ts`, `node22-ts` | `index.ts` |
| `bun` | `main.ts` |

For the `dockerfile` runtime, your Dockerfile defines how the program starts.

## Using more than one collection from an Aetherfy agent

There is no list form of `database_collection`, and none is needed. The field is
a single default-collection **hint** recorded on the agent — it does not restrict
what the agent can reach at runtime.

Aetherfy injects `AETHERFY_WORKSPACE` into every agent, and the SDKs use it as
the namespace. Any collection in that workspace is reachable by name, with no
configuration:

```python
from aetherfy_vectors import AetherfyVectorsClient

client = AetherfyVectorsClient(workspace="auto")

client.search("animals", vector)
client.search("food", vector)
client.search("toys", vector)
```

So set `database_collection` if you want the agent's primary collection recorded
on its record, and ignore it otherwise.

## Merge-patch semantics in Aetherfy

Aetherfy applies your `aetherfy.yaml` as an **RFC 7396 merge patch**, not as a
full replacement. This is the single most consequential behaviour on this page:
a field you leave out is *preserved*, not reset to its default.

| In the deployed yaml | Effect on the Aetherfy agent |
|---|---|
| Field omitted | Preserved — the existing value is untouched |
| Field with a value | Set to that value |
| Field explicitly `null` | Cleared (nullable fields only) |

So a configuration you deploy that omits `schedule` keeps whatever schedule the
agent already has. To actually remove a schedule you must write it explicitly:

```yaml
name: nightly-report
runtime: python3.12
type: job
schedule: null
```

Three fields are non-nullable, and an explicit `null` on any of them is
**rejected** rather than treated as a clear:

| Non-nullable field | What `null` does |
|---|---|
| `memory_mb` | Rejected |
| `idle_timeout_minutes` | Rejected |
| `keep_alive` | Rejected |

Merge-patch is also why a deploy from a linked GitHub repository does not wipe
settings you made elsewhere — fields absent from the pushed file keep the values
you set through the Aetherfy dashboard or API.

## Unknown keys are silently ignored by Aetherfy

The `aetherfy.yaml` parser does **not** reject unknown top-level keys. A typo
does nothing at all and the deploy still succeeds, which makes this the most
common way to believe a setting is applied when it is not.

```yaml docs-guard="skip: the typo'd keys are the point of this example"
name: nightly-report
runtime: python3.12
type: job
schedul: "0 3 * * *"   # typo: silently ignored, this agent has NO schedule
memorymb: 1024         # typo: silently ignored, memory stays at its old value
```

Both lines above are accepted and both do nothing. Note the contrast with the
Aetherfy REST API, which rejects unknown fields with a 422 — the leniency is
specific to the configuration file.

Verify before you rely on a field. `afy agents diff` previews exactly what a
deploy would change and what it would preserve:

```bash
afy agents diff nightly-report
```

It exits non-zero when there are changes, so it also works as a CI gate.

## Archive limits Aetherfy enforces

The code you upload is subject to hard limits. Exceeding them fails the deploy.

| Limit | Value | Behaviour beyond it |
|---|---|---|
| Compressed upload size | 50 MB | HTTP 413 |
| Decompressed stream ceiling | 500 MB | Rejected |
| Archive members | 20 000 | Rejected |
| Size of the yaml member itself | 1 MB | Rejected |

Keeping uploads small is mostly a matter of exclusions. The Aetherfy CLI honours
a `.afyignore` file and applies built-in defaults including `.git`, `.env`,
`__pycache__`, `node_modules`, `venv`, `.DS_Store`, and `*.log`.

## Lockfile requirements on Aetherfy

Aetherfy builds reproducibly, so some runtimes require a lockfile alongside your
manifest.

| Runtime family | Condition | Required lockfile |
|---|---|---|
| `node20`, `node22`, `node20-ts`, `node22-ts` | Always, with `package.json` | `package-lock.json` |
| `bun` | Always | `bun.lockb` |
| `python3.11`, `python3.12`, `python3.13` | When a `pyproject.toml` is present | `uv.lock` |

A Python project using a plain `requirements.txt` does not need `uv.lock`.

## Exporting an agent's current Aetherfy configuration

`afy agents pull` writes out the configuration Aetherfy currently holds for an
agent:

```bash
afy agents pull nightly-report
```

It emits the declarative subset of the fields, and re-deploying what it emits is
a no-op. Two fields are deliberately **not** emitted, because neither is stored
on the agent as durable state:

| Not emitted | Why |
|---|---|
| `regions` | Consumed per deployment rather than stored on the agent |
| `tier` | Validated then discarded — your account's plan is the real source |

Use `afy agents pull` when you want to bring an agent that was configured
through the Aetherfy dashboard back under version control.
