> ## Documentation Index
> Fetch the complete documentation index at: https://developers.telnyx.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SQL Storage

> Every agent carries a private, embedded SQLite database at this.ctx.storage.sql — relational, queryable storage next to state and history, for data that outgrows both.

`Agent` extends [`StatefulActor`](/docs/edge-compute/stateful-actors), and every actor
carries a private, durable **SQLite database** at `this.ctx.storage.sql`. There is
nothing to declare in `telnyx.toml` — the database is created on first use, and it works
inside an agent exactly as it does on a plain actor.

## Which tier holds the data?

An agent has three durable tiers, all in the same actor:

| Tier                                               | API                         | Shape                          | Reach for it when                                                           |
| -------------------------------------------------- | --------------------------- | ------------------------------ | --------------------------------------------------------------------------- |
| [State](/docs/agent-sdk/state)                     | `getState()` / `setState()` | one small value, merge-patched | current status, preferences, last-seen — read as a whole every turn         |
| [Message history](/docs/agent-sdk/message-history) | `this.messages`             | append-only conversation log   | what was said, in order, feeding the LLM                                    |
| **SQL**                                            | `this.ctx.storage.sql`      | tables, indexes, aggregates    | anything you'd `WHERE`, `GROUP BY`, or index — orders, events, tool results |

State is read and written as one value; history is read back in order. The moment you
want *"the last five orders over \$10"* — a lookup neither tier answers without a scan —
put rows in SQL.

## Using it from an agent

`exec()` is **synchronous** — the database is a local file in the actor's own process —
with positional `?` binds and a cursor you drain with `toArray()`:

```ts theme={null}
import { Agent } from "@telnyx/edge-runtime";

export class Support extends Agent {
  async recordOrder(sku: string, cents: number): Promise<void> {
    this.ctx.storage.sql.exec(
      `CREATE TABLE IF NOT EXISTS orders(sku TEXT, cents INTEGER, at INTEGER)`,
    );
    this.ctx.storage.sql.exec(
      `INSERT INTO orders(sku, cents, at) VALUES (?, ?, ?)`,
      sku,
      cents,
      Date.now(),
    );
  }

  // An answer the LLM can use as tool output — no scan, no external database
  async topSkus(): Promise<Array<{ sku: string; total: number }>> {
    return this.ctx.storage.sql
      .exec<{ sku: string; total: number }>(
        `SELECT sku, SUM(cents) AS total FROM orders
         GROUP BY sku ORDER BY total DESC LIMIT 5`,
      )
      .toArray();
  }
}
```

Everything the actor surface guarantees applies unchanged: the database is private to
this one agent instance — per conversation, if you key actors by conversation — reads
always reflect your prior writes, and [serialized
turns](/docs/agent-sdk/concepts/how-agents-run) mean no other call interleaves
mid-write. Wrap related writes in `this.ctx.storage.transactionSync(() => { ... })` to
commit them atomically.

Patterns that come up in agents:

* **Webhook dedup.** `INSERT` the event id into a table with a `UNIQUE` constraint
  before `queue()`-ing work; a thrown constraint violation means you already handled
  that event.
* **Tool-call ledger.** Record each tool invocation and result as a row; answer "what
  did you do?" or audit questions with a query instead of replaying history.
* **Searchable history.** `this.messages` is an ordered log, not an index. If the agent
  needs keyword lookup over past conversation, append each message to an SQL table too —
  in the same method that calls `messages.add()` — and query it with `LIKE` plus an
  index.

## Semantics and limits

The full contract — cursor rules, multi-statement batches, `transactionSync`, binding
types, and the limits (1 GB per actor database, 2 MiB per bound value, integer range) —
is on the actor [SQL guide](/docs/edge-compute/stateful-actors/guides/storage/sql). It
applies verbatim inside an `Agent`: the SDK reserves
[`alarm()`](/docs/agent-sdk/api-reference/agent) for its scheduler, but all of
`ctx.storage` stays yours.

For data that more than one function or caller must query — shared across agents, or
read from the CLI and REST API — use a standalone
[SQL Database](/docs/edge-compute/sqldb) instead; the embedded database is strictly
per-instance.
