---
title: "Query Budgets"
description: "Cap queries per window and rows per result for an agent credential. Runaway loops and full-table scans hit a ceiling instead of your database."
canonical: "https://pgbeam.com/docs/budgets"
last-updated: "2026-10-29T22:00:28.000Z"
---

# Query Budgets

> Cap queries per window and rows per result for an agent credential. Runaway loops and full-table scans hit a ceiling instead of your database.

URL: https://pgbeam.com/docs/budgets

A budget caps how much an agent can run. You set queries per window, a maximum number of rows per result, and a statement timeout. When an agent loops or asks for too much, the budget stops it at the wire instead of letting it hammer your database.

## Set a budget

```bash title="Budget on a policy"
pgbeam policies create --name analytics \
  --budget-queries-per-day 5000 \
  --max-rows 1000 \
  --statement-timeout-ms 10000
```

In the dashboard, set the budget on the policy profile under **Budgets**.

## What you can cap

| Limit              | Effect                                                                                                            |
| ------------------ | ----------------------------------------------------------------------------------------------------------------- |
| Queries per window | Cap statements per hour or per day. The next query past the cap is blocked with an error until the window resets. |
| Max rows           | Truncate any result to at most this many rows.                                                                    |
| Write row cap      | Hard cap on rows a single write (INSERT/UPDATE/DELETE) may affect. Set with `--max-affected-rows`.                |
| Statement timeout  | Cancel a statement that runs longer than this.                                                                    |

## What the agent sees

A query past the cap comes back as a Postgres error (SQLSTATE `53400`):

```text
ERROR:  PgBeam agent day query budget exhausted; resets at 2026-06-15T00:00:00Z; ask your operator to raise the limit
```

The message names the window and the exact reset time, so an agent can back off rather than retry blindly. A per-day egress (bytes) budget produces a similar message when exceeded.

### A warning before the cap

On a plain Postgres connection (psql, a driver, an ORM), the error is not the first signal. When a statement leaves a query window at or under a tenth of its limit, the session gets a `NOTICE` (SQLSTATE `01000`) with the same figures:

```text
NOTICE:  PgBeam agent query budget low: 20 of 200 hourly queries left; resets at 2026-06-15T14:00:00Z
```

Each window is reported once per session, and the next window warns again. When the credential has queries bought with a [budget top-up](/docs/agent-payments) still unspent, the message ends with `; N purchased queries in reserve`. The daily egress budget warns the same way, once per UTC day:

```text
NOTICE:  PgBeam agent egress budget low: 10485760 of 104857600 daily bytes left; resets at 2026-06-16T00:00:00Z
```

Egress bytes are charged after a response is sent, so that warning arrives with the statement after the one that crossed the threshold, and a single response that goes from above a tenth straight past the cap gets no warning at all.

The notice is advisory. It does not change the statement's result and writes no audit entry. Most drivers drop notices unless you register a handler (`OnNotice` in pgx, the `notice` event in node-postgres, `add_notice_handler` in psycopg), so wire one up if your agent runs long jobs. Match on the `PgBeam agent query budget low: ` or `PgBeam agent egress budget low: ` prefix rather than the whole sentence. The MCP endpoint does not forward these notices, because every tool result already carries the figures, as the next section shows.

### Headroom on the MCP endpoint

Over the [hosted MCP endpoint](/docs/mcp), an agent does not have to wait for that error to learn where it stands. Every row-returning tool result (`query`, `explain`, `list_tables`, `describe_table`) carries a `budget` block for the credential that ran it:

```json title="Tail of a query tool result"
{
  "row_count": 42,
  "budget": {
    "queries_per_hour": {
      "limit": 200,
      "used": 37,
      "remaining": 163,
      "resets_at": "2026-06-15T14:00:00Z"
    },
    "egress_bytes_per_day": {
      "limit": 104857600,
      "used": 5242880,
      "remaining": 99614720,
      "resets_at": "2026-06-16T00:00:00Z"
    }
  }
}
```

A window your policy leaves uncapped is omitted, so a present key means the cap is enforced. A credential with no budget at all gets no `budget` block. The figures are the same per-region counters the wire path enforces, so they are approximate in the same way, and the egress figure can trail the agent's last statement by a moment because bytes are charged after the response is sent. It is a signal for pacing a long job, not a billing record.

`my_permissions` carries the same `budget` block next to the caps it reports under `budgets`, and so does the `permissions` half of `briefing`. `my_permissions` opens no database session, so an agent can check what is left before a long job without spending any of it.

## Write row cap

`--max-affected-rows` bounds how many rows a single write may affect, separate from `--max-rows` (which truncates read results). A write whose affected-row count would exceed the cap runs inside a transaction, is checked, and is rolled back so nothing persists, then blocked. It applies to INSERT, UPDATE, and DELETE and is enforced independently of human approval. 0 means unlimited.

> **Per-region windows**
>
> Budget counters are kept per data plane and per region, like the rate limiter.
> An agent connects to one region in practice, so the window it sees is
> consistent. The control plane aggregates usage across regions and can trip the
> [kill-switch](/docs/kill-switch) if an organization-level ceiling is breached.

## Related

* [Policies](/docs/policies)
* [Kill-switch](/docs/kill-switch)
* [Query timeout](/docs/query-timeout)