# Tables and versions

> How rho names tables, keeps every version, and never loses data.

## Tables

A table is named by a path, such as `research/competitors`: up to five parts of letters, digits, `_` and `-`, joined by `/`. The parts before the last are its folders. Paths are lowercase, so `Research/Competitors` and `research/competitors` are one table. In SQL, a table is named by its path in double quotes:

```sql
SELECT category, count(*) FROM "research/competitors" GROUP BY 1
```

Each column has a type (text, integer, double, decimal, boolean, date, timestamp and so on), read from the data written. Values that don't all fit one type stay text exactly as written, and the write says so in a warning.

A table can be renamed, and its history, links and API follow it.

## Versions

Every write makes a new version: version 1, 2, 3 and on. A version never changes once made, so a version number always reads the same rows.

A write has a mode:

- **create**: a new table. Refused if the path is taken.
- **append**: adds the rows.
- **upsert**: updates the rows whose key columns match, and adds the rest. A column left out of a row keeps its value.
- **replace**: the rows become the table's contents.

All but create make the table if there is none. Rows whose columns don't fit the table are refused, naming the columns that conflict and how to fix them.

## Who changed what, and why

Each version records:

- who made it: an agent (by the name its client gives, such as "Claude Code"), an API token (by its name), or a person in the app;
- the `message` given with the write, such as "added 40 rows from Crunchbase";
- the `run` label, which an agent or script sets to group the writes of one task;
- when, and what changed: rows added, updated and removed, and columns added.

`table_history` answers this list, and the table's **History** panel in the app shows it with a diff of each version.

## Undo

`restore_version` makes an earlier version the latest again. It does this by making a **new** version with the old one's rows, so the versions after it are kept and the restore can be undone too.

## Trash

`delete_table` moves a table to the trash and frees its path. Its versions are kept. `restore_version` brings it back, and so does the **Trash** page in the app. Nothing deletes a table for good.

Because the tool is marked destructive, MCP clients ask their user before calling it.

## Descriptions

A table and each of its columns can have a description: what it holds, where it came from, its units. `update_table_info` sets them, and `describe_table` answers them, so the next session (or another agent) knows what it's reading. Describing a table changes no data and makes no version.