---
title: Tables and Agent Tools
tag: tables
summary: >-
  Declare what a table's columns mean, edit rows through the controls those
  kinds name, and give an agent tools that reach one table and nothing else.
---

# Tables and Agent Tools

A table holds the structured state of a workspace: books you are reading, calendar blocks, leads,
metric samples. What a row *is* comes from the columns the table declares, and that declaration is
what lets you edit a row as a form, and an agent add one by name while reaching nothing else.

## What a table is

A table is a named set of typed columns and the rows they hold. A column's declared kind decides
what a cell means: what it can hold, which control edits it, and what a write has to satisfy to be
stored. Every row also carries an `id`, which is not a column.

**A column's name is its column name.** There is no second name and no separate label: the label
you see is Title Case of the name, so `release_date` shows as "Release Date". A rename keeps the
kind and every value, and workflows, Data Views, and saved queries that reference the old name do
not follow it.

**A kind is what the column means; how it is stored follows from it.** There are fourteen, in the
order the picker offers them:

| Kind | Holds |
|------|-------|
| Text | One line of free text |
| Long text | Multi-line prose |
| Block document | Rich document content, the same format a task description uses |
| JSON | Freeform JSON: objects, lists, strings, numbers, booleans, and nested nulls |
| Tool group | One configured tool group, chosen with the shared tool picker |
| Select | Exactly one option from a closed list you define, each one optionally toned |
| Resource | A link to one workspace resource — a document, a workflow, a table, or a memory |
| Integer | A whole number |
| Decimal | A fractional number |
| Boolean | Yes or no |
| Date | A calendar day, no time |
| Date and time | An instant, stored in UTC and shown in your timezone |
| List | An ordered list of one declared entry kind |
| Object | A named shape with its own declared columns, closed to anything else |

A Select accepts only its options, an Integer rejects `3.5`, a Resource accepts only the id of a
resource of the kind it names, and an Object rejects a key it does not declare. JSON is the one
kind that checks nothing, which makes it right for data whose shape is genuinely open — a provider
response, a webhook body — and wrong for a shape you have merely not written down yet.

Five kinds carry an interior, declared beside the kind:

- **Select** declares its **Options**, each with an optional tone. With no options it can hold
  nothing.
- **List** declares what **Each entry is**.
- **Object** declares its own columns, which may nest further.
- **Resource** declares what it **Points at** — a document, a workflow, a table, or a memory.
- **Tool group** declares the **Surface** its tools run on, which is what the picker offers.

## Declaring a reading list

**1. Create the table.** Open **Tables**, choose **Create Table**, and set **Name** to
`Reading List`; the handle `reading-list` is minted from the name, and **Handle (optional)** takes
a different one where the minted one is wrong.

**2. Declare the columns.** Each row of the **Columns** editor takes a name, a kind, a **Required**
switch, and a description:

```text
title      Text
author     Text
status     Select — unread, reading, finished
rating     Integer
added_at   Date and time
```

`status` as a Select rather than Text is the difference between an agent that writes `finished` and
one that writes `Finished`, `done`, or `read`.

**3. Add a row.** Open the table, choose **Add Row**, and fill the form. The write is checked
against the declaration, so a rating of `4.5` or a status of `abandoned` is refused rather than
stored.

**Note**: the grid opens locked. Existing cells are read-only, and **Add column** and **Edit
column** are disabled, until you open the lock beside **Rows**.

The assistant does all three, and reaches for the narrowest kind that fits:

> Create a reading-list table with a title, an author, a status of unread/reading/finished, an
> integer rating, and an added timestamp.

## Changing a declaration

Columns are added, renamed, retyped, and dropped from **Add column** and **Edit column** in the
grid's column headers, or by asking the assistant.

**A change within one storage class is instant.** Text to Select, Select to Long text, Decimal to
Integer: the column does not move and no stored value is rewritten.

**A change the stored values do not satisfy is refused, naming the row that disagrees.** Narrowing
a Select while a row still holds the option you are removing fails, and so does tightening Decimal
to Integer over a fractional value. Repair or clear those rows, then make the change.

**Dropping a column that still holds values is refused.** Clear it deliberately first.

**Note**: a new Required column on a table that already has rows is refused, because the rows
already there cannot satisfy it. Add it optional, backfill it, then make it required.

## Editing rows

In the grid, each cell is edited by the control its kind names.

- A **Select** is a dropdown. The chosen option reads Title Cased behind a dot in its tone, and
  reads `Not set` while an optional column is empty.
- A **Date** and a **Date and time** open a picker — a calendar day for one, a day and a time for
  the other, shown in your timezone.
- A **Resource** is a chip naming its target, with a picker to retarget it. The row stores the id
  alone and the name is resolved from the target, so a rename keeps up.
- A **Block document** is the document editor, inline in the cell.
- **Long text** is a textarea, **Boolean** a switch, and **Integer** and **Decimal** number inputs
  stepping by 1 and 0.1.

JSON, List, and Object have no cell-sized control: the cell previews the value and opens a full
editor from the pencil. The **Add Row** form has the room, so there an Object unfolds into its own
declared columns and a List into its entries.

## When a row breaks the declaration

Every write is checked against the declaration, so nothing you write through a table creates a bad
row. The database underneath checks the storage rather than the meaning — no column of it can test
select membership or an object's shape — so a row that predates the declaration it is now read
against can still violate it.

That produces one broken row, not a broken table. A read tags every row with its state:

- **Valid** — satisfies the declaration.
- **Broken** — does not, and says which column and why. It keeps its place in the grid as
  **Broken row**, with **Show values** for the raw values, **Repair** to edit them, and
  **Delete row**.
- **Unreadable** — carried no readable id, so it is shown but cannot be repaired or deleted from
  the grid.

A read never fails as a whole and never drops a row, and a broken row is counted in the total, so
an agent asked how many books are on the list cannot undercount because one row is malformed. A
repair is checked like any other write: a fix that still breaks the declaration is refused rather
than stored.

## Scoped tools over one table

An agent working from the workspace table tools — `list_tables`, `query_table`, `insert_table_row`
and the rest — has to work out which table you meant and read its columns first, and those tools
reach every table in the workspace. They are the right tools for authoring and ad-hoc work, and the
wrong ones for an agent whose job is your reading list.

The **Table Tools** node replaces both problems with one dropdown.

**4. Add a Table Tools node.** Set **Table** to `Reading List` and leave the operation checkboxes
as they are.

It composes one tool per enabled operation, prefixed with the table's name in snake_case:

```text
reading_list_query    filter, sort, and page rows
reading_list_get      read one row by id
reading_list_add      add one row
reading_list_update   change columns of one row
reading_list_remove   delete one row — off by default
```

Each parameter schema is generated from the table's columns, so `reading_list_add` shows the agent
`title`, `author`, `status` as an enum of its three options, `rating` as an integer, and `added_at`
as a timestamp. A List arrives as a typed array and an Object as a closed shape. These tools cannot
reach another table, and cannot write a column the table does not declare.

**5. Wire it into the agent.** Connect the node's `tools` socket to the AI node's `tools` socket, or
into the **Flatten** node feeding it if other tool providers are already there.

**Note**: one node targets one table. A second table is a second node — the tool names differ, so
both feed the same AI node.

Set **Tool Name** when the derived prefix collides with another node's, or when the table's name
makes a poor tool name. Every generated name has to be ASCII letters, numbers, underscores, or
hyphens, and at most 64 characters; the node reports a name it cannot generate when you save the
workflow, rather than failing mid-conversation.

A table's **AI access** settings gate all of this. With **AI tools can read** or **AI tools can
write** off, these tools refuse that operation; a scoped tool grants nothing the table withholds.

## Next steps

[Data Views](/guides/verdalia-data-views) put a table's rows on a Pegboard as a table, a card list,
or a checklist.
