Aetherfy agent commands
Agents are the default noun in the Aetherfy CLI
A bare verb is an agent verb. afy list lists your Aetherfy agents, afy status <name> describes one, and afy stop <name> pauses one. Only the other kinds of
object keep a group of their own: afy secrets, afy workspaces, afy github,
and the vector groups afy collections, afy index and afy points.
Each of these commands is spelled exactly one way. There is no afy agents
group and no alias for one.
| Subcommand | Purpose |
|---|---|
afy list | List every agent in the account |
afy create <name> | Create an agent record |
afy delete <name> | Delete an agent permanently |
afy stop <name> | Pause an agent |
afy start <name> | Resume a paused agent |
afy archive <name> | Free the plan quota slot, keeping configuration and code |
afy restore <name> | Re-provision an archived agent |
afy cancel <name> | Cancel a pending deployment |
afy status <name> | Show detailed agent status |
afy rename <current> <new> | Rename an agent |
afy update <name> | Change workspace assignment or description |
afy access <name> | Show or change the workspaces the agent’s key can use |
afy pull <name> | Export the agent’s configuration as aetherfy.yaml |
afy diff | Compare local aetherfy.yaml against deployed state |
afy run <name> | Run an agent once, immediately |
afy runs <name> | Show run history |
afy schedule pause <name> | Pause the agent’s scheduled task |
afy schedule resume <name> | Resume the agent’s scheduled task |
Two things do not exist in the Aetherfy CLI and are commonly assumed:
- There is no
afy schedule set. A schedule is declared with theschedule:key inaetherfy.yamland applied byafy deploy. See /agents/scheduled-tasks. - There is no
afy pauseorafy resume. The pause and resume verbs for an agent areafy stopandafy start.
Listing Aetherfy agents
afy list prints every agent in the account. It takes no arguments and
no flags of its own.
afy list
afy list -o jsonThe default columns are:
| Column | Meaning |
|---|---|
| Name | The agent name |
| Type | SERVICE or JOB |
| Status | Current lifecycle status |
| Regions | The regions the agent is deployed in |
| ID | The Aetherfy agent ID |
When at least one agent in the account has a scheduled task, the table grows to eight columns: Name, Type, Status, Regions, Schedule, Next Run, Last Run, ID.
| Column | Rendering |
|---|---|
| Schedule | The declared schedule expression |
| Next Run | A UTC timestamp, or (paused) when the schedule is paused |
| Last Run | The most recent run’s outcome and how long ago it started, such as failed, 2m ago, or never. When the schedule’s last tick did not start a run, a second line says so: tick skipped (5m ago) or tick missed (5m ago). A tick that fired adds nothing — its run is the first line. The reason a run failed is printed by afy status |
A status of usage_paused renders as paused (usage limit) — the agent was
paused by the Aetherfy usage meter, not by afy stop.
Creating an Aetherfy agent
afy create <name> creates the agent record. It takes exactly one
positional argument, the name.
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--description | -d | string | empty | Agent description |
--type | -t | string | SERVICE | Agent type: SERVICE or JOB |
--runtime | -r | string | python3.11 | Runtime: python3.11, python3.12, python3.13, node20, node22, node20-ts, node22-ts, bun, dockerfile |
--spawn-enabled | bool | false | Enable spawning for this agent. Requires --spawn-workers | |
--spawn-workers | strings | none | Agents this agent may spawn, comma-separated, or '*' (quoted, so the shell does not expand it) for any of your agents |
--spawn-enabled without --spawn-workers is refused with
AGENT_SPAWN_WORKERS_REQUIRED; why the workers are listed is on
the aetherfy.yaml page.
--type accepts only SERVICE or JOB; anything else is rejected.
afy create scraper \
--type JOB \
--runtime python3.12 \
--description "Nightly catalogue scrape"afy create has no --workspace flag. To put a new Aetherfy agent in
a workspace, either run afy update <name> --workspace <ws> afterwards,
or declare the workspace: key in aetherfy.yaml before deploying.
This is not the only way to create an agent. afy deploy will also create the
agent it targets — from the type and runtime in aetherfy.yaml, and only
with explicit consent — so a first deploy need not be preceded by this command.
See /cli/deploy.
Deleting an Aetherfy agent
afy delete <name> permanently deletes the agent.
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--force | -f | bool | false | Skip confirmation prompt |
Without --force the Aetherfy CLI asks for confirmation and requires you to type
the agent name exactly before it proceeds.
afy delete scraper
afy delete scraper --forceIf your goal is to free a plan quota slot rather than lose the agent, use
afy archive instead — it preserves the configuration and the stored code
bundle.
Pausing and resuming an Aetherfy agent with stop and start
afy stop <name> pauses an Aetherfy agent. It stops every machine and
prevents the proxy from re-waking the agent on incoming traffic. The pause is
reversible.
afy start <name> resumes an agent that was paused with stop.
afy stop api-gateway
afy start api-gatewayFor a service agent, afy start returns once every machine has booted and
waits, up to a bound, for your code to answer its health check, and says what it
found. A job agent is resumed without starting a machine — its machines wake for
runs — so afy start says only that it resumed:
readiness | What afy start tells you |
|---|---|
serving | The agent resumed and is serving requests |
starting | The agent resumed; your code is still starting, and requests sent now are held until it is listening |
load_failed | The agent resumed, but your code failed to load, so its requests will fail — afy logs <name> has the error |
unconfirmed | The agent resumed and its machines started, but your code was not confirmed serving: it did not answer in the time allowed — afy logs <name> shows where it is — or you archived, deleted or paused the agent while afy start was answering, and its machines were being stopped |
slow_start | The agent resumed, but starting its machines took longer than it should, so your code was not checked — the delay was on Aetherfy’s side |
null | A job agent, which serves no requests: it says only that the agent resumed |
The agent is resumed in every case, and the command exits 0 — these describe your code, not the resume. The same field is in the API response, described in Stopping and starting.
When a machine’s host has no room to resume it, the start is accepted too: afy start
says the agent is being started on a new machine, how long that can take at most, and
exits 0. With --wait it follows the agent as below, and once the agent is running it
says it was a cold start — a new machine from the agent’s current release, not a resume
of the old one. The details are in
Starting when a machine’s host is full.
Run afy start right after afy stop and the stop may still be finishing — your
server gets its shutdown grace. The start is then accepted, not refused: the command
says the agent will start once its previous stop finishes, how long that can take at
most, and exits 0. Aetherfy starts the agent itself when the stop completes, so there is
nothing to re-run, and interrupting the command loses nothing. afy status <name> shows
the pending start until the agent is running, or why it was dropped. With --wait,
afy start follows the agent until it is running — for as long as the server says the
start can take — and exits 1 with the reason if the start was dropped (an afy stop, an
archive or a delete, a billing hold, or a stop or start that failed), so a script can go
on to send the agent requests. If that time passes with the start still pending, it
says Aetherfy is still retrying and that afy status <name> shows the result, and exits
4: the start has not failed, and Aetherfy carries it out on its own. The details are in
Starting while the previous stop is still finishing.
Waiting for an Aetherfy agent to start with —wait
--wait matters only when the start is accepted rather than carried out at once; a
start that boots the agent straight away answers as in the table above, with or
without it. A script that has to send the agent requests next stops it, starts it
with --wait, and branches on the exit code:
afy stop api-gateway
afy start api-gateway --wait
case $? in
0) echo "running" ;;
4) echo "still being started by Aetherfy" ;;
*) echo "not started" ;;
esacA start accepted while the stop was still finishing, carried out within the bound
(exit 0). The bound is the server’s own wait_bound_seconds for this start, so the
number you see differs:
✓ Agent 'api-gateway' will start once its previous stop finishes (at most 279s).
✓ Agent 'api-gateway' resumed.A start on a full host, carried out on a new machine (exit 0):
✓ Agent 'api-gateway' is being started on a new machine: its machine's host has no room to resume it, so Aetherfy recreates it from the current release (at most 413s).
✓ Agent 'api-gateway' resumed.
Its machine's host had no room to resume it, so it was started on a new machine from its current release: a cold start.A start still pending when the bound has passed (exit 4). The agent is read every 5 seconds, so the time it names is the bound rounded up to the next read:
✓ Agent 'api-gateway' will start once its previous stop finishes (at most 279s).
Warning: Agent 'api-gateway' has not started within 280s. Aetherfy is still retrying the start; 'afy status api-gateway' shows the result.
Error: agent 'api-gateway' has not started within 280s; Aetherfy is still retryingA start that was dropped exits 1, with the reason, as described above.
afy stop takes no flags; afy start takes only --wait. A stopped Aetherfy agent
still bills at the base rate — stopping suspends execution, not billing. To stop billing
for an agent you are not using, archive it.
Archiving and restoring an Aetherfy agent
afy archive <name> destroys the underlying application to free the plan
quota slot while preserving the agent’s configuration and its stored code bundle.
It is reversible.
afy restore <name> re-provisions an archived Aetherfy agent from the
preserved bundle.
afy archive old-worker
afy restore old-workerRestoring consumes a plan quota slot, and the quota is re-checked at restore time. If you are already at your plan limit the restore is rejected until you free a slot or upgrade. Plan quotas are documented at /platform/limits.
afy restore returns once the restore is accepted. It is checked again when it
runs, and a restore refused then leaves the agent archived: afy status <name>
prints a Reason line saying why and what to do. afy list shows the status
alone, which reads the same for a restore still running and a refused one.
Cancelling a pending Aetherfy deployment
afy cancel <name> cancels a pending deployment for the agent. It takes
no flags.
afy cancel scraperOnly deployments in the QUEUED state are cancellable. A deployment whose build is
already in flight returns HTTP 409 from the Aetherfy API and cannot be cancelled;
wait for it to finish, then roll back with afy rollback if the result is wrong.
Inspecting an Aetherfy agent with status
afy status <name> prints the detailed state of a single Aetherfy agent.
It takes no flags of its own and honours -o json.
afy status scraper
afy status scraper -o jsonThe text output covers:
| Field | Notes |
|---|---|
| ID | The Aetherfy agent ID |
| Name | The agent name |
| Type | SERVICE or JOB |
| Status | Current lifecycle status, with health or degraded detail when present |
| Reason | Why the agent is not serving, when Aetherfy recorded a reason: its app or its image is no longer on the compute plane, every running machine of it is failing its health check (with the error the machine reports), or the compute plane no longer runs any of its machines. Absent otherwise |
| Regions | The regions the agent is deployed in |
| Spawn Enabled | Whether this agent may start child agents |
| Workspace | The workspace the agent belongs to, if any |
| Schedule | The schedule expression, labelled UTC |
| Next run | Next scheduled fire time, or (paused) |
| Last tick | What the schedule last did when it did not start a run — skipped or missed — with the reason Aetherfy recorded. Absent when the last tick fired |
| Last run | The most recent scheduled or manual run: its outcome, the reason when it failed, and how long ago it started, as in “failed (run exited with code 1), 2m ago”. never for a scheduled agent that has not run |
| Created | Creation timestamp |
| Updated | Last-modified timestamp |
| Description | The agent description |
| Spawn relationships | Parent and child agents |
| Repo, Branch, Directory, Webhook id | The agent’s GitHub link, when it has one |
The GitHub link in status
An agent linked to a GitHub repository with
afy github link also gets its link printed: the repository, the
branch that is watched, the directory inside it — repository root when the
link has none — and the webhook id. An agent with no link prints none of this.
Two states leave that link intact and stop deploying anything, and afy status
is where the terminal can learn about either:
| What status says | What happened | What fixes it |
|---|---|---|
| GitHub disconnected — pushes are not deploying | The Aetherfy account is no longer connected to GitHub | afy github connect. The link is kept, so deploys resume on reconnect |
| Branch deleted — pushes are not deploying | The branch the link watches was deleted, and status names which and when | Recreate the branch and push. Relinking would succeed and change nothing |
Neither state is reported on GitHub. Aetherfy announces a skipped push as a check on the commit, which needs the installation token a disconnect removes, and a deleted branch has no commit to attach one to at all.
A push that arrived while a deploy of the agent was in progress waits for it, and has no deployment until it starts (pushing while a deploy is running). While one waits, status prints it:
Push waiting: 9f8e7d6, received 2026-09-26 12:00 UTC — deploys when the deploy in progress endsA tick that fired has started a run, and says nothing about how that run went:
the Last run line does. Read afy runs <name> for the runs before it.
afy status <name> -o json carries the last run under a last_run key — id,
trigger_source, state, created_at, error_message and release_version —
and the link state under a github key, with
the API’s own field names — linked, repo, branch, root_dir,
webhook_id, account_connected, branch_deleted_at and waiting_push
(sha and received_at, or null when no push waits). The link is part of
the agent record afy status reads rather than a separate request, so a status
that printed has no second read that could have failed.
Renaming an Aetherfy agent
afy rename <current-name> <new-name> changes the agent’s name. It takes
exactly two positional arguments, which must differ.
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--force | -f | bool | false | Skip confirmation prompt |
afy rename scraper catalogue-scraper --forceAn Aetherfy agent’s URL is fixed when the agent is FIRST DEPLOYED, and a rename does not move it. Existing integrations, links, and webhooks keep working across a rename; only the name changes.
The practical rule: rename before your first deploy and the URL follows the new name, rename after and it stays as it was.
The rename also updates your local aetherfy.yaml. afy deploy finds its
target through the name: key in that file, so a rename that stopped at the
Aetherfy server would leave the file naming an agent that no longer exists — and
the next deploy from that directory would report the agent as not found, then
offer to create one under the old name.
afy rename closes that gap and tells you which branch it took:
| What is in the current directory | What happens |
|---|---|
An aetherfy.yaml whose name: is the OLD name | That one key is rewritten. Comments, key order, quoting and line endings are left alone |
An aetherfy.yaml naming a different agent | Nothing is touched. That file belongs to another project, and rewriting it would retarget its deploys |
No aetherfy.yaml, or one with no name: key | Nothing is touched |
In the last two cases the command names the file you still have to update. Set
name: to the new name in whichever aetherfy.yaml you deploy this agent from,
or that deploy will not find it.
The dashboard has no rename button (what the dashboard does not do), and that is the reason: a browser cannot reach the file on your machine, so it cannot finish the job.
Updating an Aetherfy agent’s workspace or description
afy update <name> changes the mutable metadata of an Aetherfy agent.
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--workspace | string | empty | Assign the agent to this workspace | |
--no-workspace | bool | false | Clear the agent’s workspace (make it workspaceless) | |
--description | -d | string | empty | Set the agent’s description |
Rules enforced by the Aetherfy CLI:
- At least one flag is required; with none, the command errors.
--workspaceand--no-workspaceare mutually exclusive.--workspace ""is rejected. Use--no-workspaceto clear the assignment.
afy update scraper --workspace research
afy update scraper --no-workspace
afy update scraper -d "Nightly catalogue scrape"This is also the supported way to move an existing Aetherfy agent into a
workspace, because afy create has no --workspace flag.
Granting an Aetherfy agent another workspace with access
afy access <agent> shows, and changes, which workspaces an agent’s key can use on
the Aetherfy vector API: its own workspace, plus the ones you grant it. Any other
workspace is refused to the agent. See
what an agent’s key reaches.
# What can the agent reach?
afy access support-bot
# Grant it a second workspace; the command shows what becomes reachable and asks first
afy access support-bot --add research
# From a script: grant the collections in no workspace and revoke another workspace
afy access support-bot --add "" --remove archive --yes --json| Flag | Default | Effect |
|---|---|---|
--add | — | Grant a workspace. Repeat it, or give a comma-separated list. "" names the collections in no workspace; Windows PowerShell 5.1 drops an empty argument, so there write --add= |
--remove | — | Revoke a granted workspace, the same way; --remove= for the collections in no workspace on Windows PowerShell 5.1 |
--yes, -y | false | Grant without asking |
--json | false | Print one JSON object: the agent’s resulting access, and what was added and removed |
Granting widens what the agent’s code can read and write, so --add first prints
what would become reachable and asks Proceed? [y/N]. Without a terminal, or with
--json, --yes is required: the command exits 2 and sends nothing otherwise.
Removing narrows access and asks nothing, and a change that changes nothing says so
and asks nothing. The agent’s own workspace is not a grant: adding or removing it
is refused, exit 2, before anything is sent. So is an --add or --remove value
starting with -: it is what a dropped "" leaves, the next argument read as the
value, and the refusal names the = form.
A change takes effect within 60 seconds, and a grant survives the agent’s redeploys. Only your own key can run this command: an agent’s key is refused it.
afy pull <agent-name> exports the agent’s current configuration in
aetherfy.yaml form. By default it writes to stdout so it can be redirected.
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--output | -o | string | empty | Write the YAML to this file instead of stdout |
Warning: on afy pull alone, -o means an output file path, not
the global Aetherfy output format. afy pull scraper -o json writes a
file named json. This subcommand shadows the global --output flag.
# To stdout
afy pull scraper
# Redirect
afy pull scraper > aetherfy.yaml
# Or write directly
afy pull scraper -o aetherfy.yamlThe emitted YAML is the declarative subset only — fields the Aetherfy server derives are excluded. Re-deploying the pulled file is therefore a no-op against the agent it came from.
Diffing local aetherfy.yaml against deployed Aetherfy state
afy diff compares the local aetherfy.yaml against the agent’s current
state on Aetherfy and prints what a deploy would change under merge-patch
semantics. It takes no positional arguments — the agent is identified from
the manifest.
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--path | -p | string | . | Project directory containing aetherfy.yaml |
afy diff
afy diff --path ./services/scraperThe output legend:
| Marker | Meaning |
|---|---|
~ field: old → new | The deploy would change this field |
+ field: value | The deploy would set this field |
- field | The deploy would clear this field |
= field: value | No-op — local and deployed values match |
field: value | Preserved — not present locally, left untouched |
A runtime change is annotated (immutable — deploy will reject), because the
Aetherfy runtime cannot be changed on an existing agent.
afy diff exits non-zero when there are changes, which makes it a
drift gate in CI:
afy diff --path ./services/scraper
if [ $? -ne 0 ]; then
echo "Deployed Aetherfy state has drifted from aetherfy.yaml"
exit 1
fiRunning an Aetherfy task agent on demand
afy run <name> runs an agent once, immediately, outside its schedule. A
type: job agent runs its entrypoint; a type: service agent is sent one
request to its POST /aetherfy/run route — see
/agents/task-contract.
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--payload | -p | string | empty | JSON payload to pass to the run |
--payload-file | -f | string | empty | Read the JSON payload from a file |
--wait | bool | false | Wait for the run to finish (exit 1 if it fails) |
--payload and --payload-file are mutually exclusive, and the payload must be
a JSON object.
# Fire and forget
afy run scraper
# With an inline payload
afy run scraper --payload '{"since":"2026-08-01","full":true}'
# With a payload file
afy run scraper --payload-file ./payload.json
# Block until the run finishes; non-zero exit if it fails
afy run scraper --waitWithout --wait, the Aetherfy CLI prints the Run ID and suggests
afy logs <name> --run <id>. With --wait, it polls for up to 30 minutes; a
failed run exits 1, and on timeout the run keeps going on Aetherfy — only the
waiting stops.
Error hints the Aetherfy CLI prints:
| Error code | Meaning and fix |
|---|---|
AGENT_NOT_DEPLOYED | The agent exists but has no deployment. Deploy it first: afy deploy. |
Listing Aetherfy run history
afy runs <name> shows the run history for an agent, newest first.
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--limit | int | 20 | Maximum number of runs to show (max 100) |
afy runs scraper
afy runs scraper --limit 100
afy runs scraper -o jsonColumns:
| Column | Meaning |
|---|---|
| When | When the run was started, in UTC and labelled so — the same instant -o json gives as created_at |
| Trigger | What started it — cron for a scheduled fire, or a manual trigger |
| State | Run outcome |
| Release | The release the run executed, as afy deployments numbers it (v3). A dash when none was recorded: a service run that was never dispatched, or a run older than the field |
| Duration | Wall-clock run time |
| Run ID | Pass to afy logs <name> --run <id> |
Only scheduled and manual runs appear here. Runs started by a parent agent
through afy spawn belong to the parent’s history and are deliberately excluded
from the child’s list — see /cli/spawn.
Pausing and resuming an Aetherfy scheduled task
afy schedule pause <name> pauses the agent’s scheduled task. No
scheduled runs fire until it is resumed. Manual runs via afy run are
unaffected. The command is idempotent — pausing an already-paused schedule
succeeds.
afy schedule resume <name> resumes a paused scheduled task, and is
likewise idempotent.
afy schedule pause scraper
afy schedule resume scraperBoth subcommands require the agent to have a schedule. Against one that does
not, Aetherfy answers 422 AGENT_SCHEDULE_NOT_SET and the CLI prints the fix:
add a schedule: key to aetherfy.yaml and deploy.
On resume the next run time is recomputed from now. Occurrences that elapsed while the schedule was paused are skipped, never backfilled — Aetherfy does not replay a missed window.
Both subcommands print whether anything actually changed, the resulting paused
flag, and the next run time (or (paused)). Both honour -o json.
Neither command edits aetherfy.yaml. The schedule expression itself is
declared there and applied by afy deploy; see
/agents/scheduled-tasks and
/agents/aetherfy-yaml.