---
slug: agents/runs-and-logs
title: Runs and logs
kind: reference
surface: agents
summary: Reference for Aetherfy run history and logs — listing runs with afy agents runs, the run and deployment states, agent status values, every afy logs flag, and the 7-day retention and per-line caps Aetherfy enforces.
sources:
  - aetherfy-cli:cmd/agents.go
  - aetherfy-cli:cmd/logs.go
  - aetherfy-control-plane:models/deployment.py
  - aetherfy-control-plane:models/agent_log.py
  - dashboard/pages/api/controlplane/runs.js
  - dashboard/pages/api/controlplane/logs.js
---

# Runs and logs

## Listing the runs of an Aetherfy agent

A **run** on Aetherfy is one ephemeral execution of a `type: job` agent. List
recent runs newest-first:

```bash
afy agents runs nightly-report
afy agents runs nightly-report --limit 50
```

The default limit is 20 and the maximum is 100.

The listing shows five columns:

| Column | Contains |
|---|---|
| When | When the run was created |
| Trigger | Why it ran — printed as the raw value, literally `cron` or `manual` |
| State | How it ended, or where it is now |
| Duration | How long the machine ran |
| Run ID | The identifier to pass to `afy logs --run` |

Aetherfy lists only runs with a trigger source of `cron` or `manual`. Runs
started by a parent agent are excluded from this listing — they belong to the
parent's history instead.

## What the Aetherfy runs API returns

The equivalent Aetherfy REST call is:

```text
GET /agents/{id}/runs
```

| Query parameter | Accepted values | On anything else |
|---|---|---|
| `trigger_source` | `cron` or `manual` only | 422 |
| `limit` | 1–100 | 422 |
| `before` | An ISO-8601 keyset cursor on creation time | 422 |

Each row Aetherfy returns carries these fields:

| Field | Contains |
|---|---|
| `id` | The run's identifier |
| `trigger_source` | `cron`, `manual`, or `spawn` |
| `state` | See the state table below |
| `created_at` | When the run was created |
| `error_message` | The failure reason, when the run failed |
| `machine_started_at` | When the machine began executing |
| `machine_stopped_at` | When the machine stopped |
| `duration_seconds` | Execution duration |

`before` is a keyset cursor rather than an offset, so paging backwards through a
long history stays correct even as new runs arrive.

## Run and deployment states on Aetherfy

Aetherfy uses one state vocabulary for both deployments and runs. Four of the
states are terminal — once a record reaches them it will not change again.

| State | Meaning | Terminal |
|---|---|---|
| `queued` | Waiting in the build queue | No |
| `building` | The image is building | No |
| `deploying` | Machines are launching | No |
| `active` | Serving, or executing in the case of a run | No |
| `completed` | A run's machine ended on its own with exit code 0 | Yes |
| `failed` | The build, the deploy, or the run failed | Yes |
| `superseded` | Replaced by a newer successful deployment | Yes |
| `rolled_back` | Replaced by a rollback | Yes |

`completed` applies to runs, which end by design. A long-lived `service`
deployment that is working sits at `active` and stays there until a newer
deployment supersedes it.

What decides `completed` versus `failed` for a run is the process exit code —
see [/agents/task-contract](/agents/task-contract).

## Agent status values on Aetherfy

An agent has its own status, separate from the state of any individual
deployment or run.

| Status | Meaning |
|---|---|
| `pending` | Created, not yet built |
| `building` | An image is building |
| `deploying` | Machines are launching |
| `running` | Deployed and operating |
| `failed` | The most recent deployment failed |
| `stopped` | Stopped with `afy agents stop` |
| `paused` | Paused |
| `usage_paused` | Paused by Aetherfy at a spend limit — shown as paused for usage |
| `archived` | Archived, freeing the plan quota slot |
| `deleting` | Deletion in progress |
| `deleted` | Deleted |

`usage_paused` is the one to recognise on sight: the agent is not broken and
nothing needs repairing in your code. Raise the limit or settle the payment at
[https://app.aetherfy.com/dashboard/settings/billing](https://app.aetherfy.com/dashboard/settings/billing)
and the agent resumes.

## Retrieving logs from Aetherfy

Everything an agent writes to stdout and stderr becomes its logs on Aetherfy.

```bash
afy logs nightly-report
```

Scope the output to a single run, using a Run ID from `afy agents runs`:

```bash
afy logs nightly-report --run 01JABCDEF0123456789ABCDEF
```

| Flag | Short | Default | Effect |
|---|---|---|---|
| `--tail` | `-n` | 50 | How many lines to return. The server maximum is 1000. |
| `--follow` | `-f` | off | Keep streaming new lines as they arrive |
| `--since` | — | — | Only lines newer than a duration, e.g. `1h`, `30m` |
| `--level` | — | — | Comma-separated severities, e.g. `ERROR,WARN` |
| `--stream` | — | — | Comma-separated streams: `stdout`, `stderr`, `system` |
| `--run` | — | — | Restrict to a single run id |

Combined:

```bash
afy logs nightly-report --tail 200 --since 1h --level ERROR,WARN --stream stderr
```

One caveat worth knowing before you debug against it: on Aetherfy `--follow`
polls, and it **ignores** both `--tail` and `--since`. It also does not honour
JSON output. Use `--follow` to watch what happens next; use `--tail` and
`--since` to look at what already happened.

## Log record fields on Aetherfy

Every log record Aetherfy stores carries:

| Field | Contains |
|---|---|
| agent | Which agent emitted it |
| deployment | Which deployment was running |
| timestamp | When the line was emitted |
| stream | `stdout`, `stderr`, or `system` |
| level | The severity |
| message | The line itself |

The `system` stream holds lines Aetherfy itself emits about the machine, as
distinct from your program's own output on `stdout` and `stderr`.

## What Aetherfy does not record for you

Aetherfy logging is plain `stdout`/`stderr` capture, and nothing more. It does
**not** auto-instrument calls your agent makes to model providers — there is no
automatic capture of prompts, completions, token counts, latency or tool calls
from an OpenAI, Anthropic or other SDK running inside your agent.

If you want that, log it yourself. A `print()` or `logger.info()` around the call
lands in exactly the same place as the rest of your output and is searchable with
`afy logs --level`:

```python
import json, time

started = time.monotonic()
response = call_your_model(prompt)
print(json.dumps({
    "event": "llm_call",
    "model": "your-model-id",
    "elapsed_ms": round((time.monotonic() - started) * 1000),
    "prompt_chars": len(prompt),
}))
```

Keep the per-line and per-deployment limits below in mind if you log full
prompts — they are easy to exceed.

## Log limits and retention on Aetherfy

Aetherfy caps log volume in several dimensions. Logs are an operational aid, not
a durable data store — write anything you need to keep to a real destination.

| Limit | Value |
|---|---|
| Retention | 7 days |
| Per deployment | 5 MB |
| Per line | 4 KB |
| Chunks per minute, per agent | 60 |
| Per upload body | 256 KB |

When an agent exceeds the per-line or volume caps, the Aetherfy log forwarder
backs off and emits a marker in the stream so the gap is visible rather than
silent:

```text
[SYSTEM] N log line(s) dropped
```

If you see that marker, the agent is logging faster than Aetherfy will accept.
Reduce per-line size or log volume — a very large payload dumped per iteration
is the usual cause.
