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

# Write Data Store

> Insert, update, delete, upsert, batch, and bulk-load rows in a structured knowledge base

## What it does

`write_data_store` changes rows in a [data store](/features/data-stores) (a
structured knowledge base). It supports single-row and multi-row writes, one-shot
keyed saves (`upsert`), and atomic multi-statement `batch` writes. All values are
parameterized, and the tool can never run schema changes — that's
[`manage_data_store`](/tools/manage_data_store)'s job.

<Info>
  For an overview of structured data stores, see [Data Stores](/features/data-stores).
</Info>

## Parameters

| Parameter           | Type    | Required           | Description                                                                                        |
| ------------------- | ------- | ------------------ | -------------------------------------------------------------------------------------------------- |
| `knowledge_base_id` | integer | Yes                | The structured knowledge base to write to                                                          |
| `operation`         | string  | Yes                | `insert`, `update`, `delete`, `upsert`, `batch`, or `bulk_load`                                    |
| `table`             | string  | All except `batch` | Target table                                                                                       |
| `rows`              | array   | `insert`           | Rows to insert (each an object of column → value)                                                  |
| `set`               | object  | `update`           | Columns to change                                                                                  |
| `filters`           | array   | `update`, `delete` | Conditions (at least one required) — `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `like`, `isnull` |
| `row`               | object  | `upsert`           | The full row to insert-or-update                                                                   |
| `conflict_keys`     | array   | `upsert`           | Columns identifying the row (need a unique index)                                                  |
| `if_match`          | object  | No                 | `upsert` compare-and-swap: only update if the current row still matches                            |
| `operations`        | array   | `batch`            | Up to 20 mutations committed together, all-or-nothing                                              |
| `file_path`         | string  | `bulk_load`        | Sandbox path to a JSONL file (one JSON object per line). Max 100MB / 500k rows                     |
| `mode`              | string  | No                 | `bulk_load`: `insert` (append, default) or `upsert` (insert-or-update on `conflict_keys`)          |

## Key behaviors

* **`upsert` is the way to save keyed rows** — "save this user's score for this
  deal" is one call, with no select-first and no race. It needs a unique index on
  the conflict keys. The result reports whether the row was `inserted` or
  `updated`.

* **Optimistic concurrency** — pass `if_match` with the version you last read
  (e.g. an `updated_at`); if another writer changed the row first, the write is
  skipped and the current row is returned so you can reconcile.

* **`batch` is atomic** — several mutations in one transaction, all committed or
  none. Use it for coordinated multi-row changes.

* **`update` and `delete` require at least one filter** — a guard against
  accidentally rewriting a whole table.

* **Errors carry the raw Postgres code** (for example `23505` for a unique
  violation) plus the constraint name, so callers can branch on the code rather
  than parse a message.

* **`bulk_load` is file transport** — for rows the agent is *moving* (imports, daily
  syncs from an external system), not reasoning about. The agent writes JSONL to the
  sandbox with `execute_python`, then one `bulk_load` call streams it server-side in a
  single transaction — the rows never pass through model context. `mode: upsert` with
  `conflict_keys` makes a daily refresh idempotent. More than 5 malformed lines aborts
  the whole load rather than half-loading a garbled file.

## Common use cases

### Insert rows

```
operation: insert
knowledge_base_id: 954
table: project_stages
rows:
  - { stage_name: Kickoff, status: pending, sort_order: 1 }
```

### Upsert a keyed row

```
operation: upsert
knowledge_base_id: 954
table: scores
row: { deal_name: Project Oak, partner_name: Sarah Chen, score: 8 }
conflict_keys: [deal_name, partner_name]
```

## Related

* [Manage Data Store](/tools/manage_data_store) — create tables, columns, views
* [Query Data Store](/tools/query_data_store) — read data with SQL
* [Data Stores](/features/data-stores) — the feature overview
