Custom agent
Connect any agent that stores its conversations in a database.
Updated 3 October 2026
Use a custom agent when your agent stores its conversations in a database. You give Benchouse a connection string and one SQL query. We run the query on a schedule as a read-only user, and read nothing else.
The columns the query returns are listed in the turns/v1 contract. Each database page has an example query.
Connection string
| Database | Connection string |
|---|---|
| PostgreSQL, Supabase, Neon, Aurora | postgres://benchouse_read:PASSWORD@HOST:5432/DB?sslmode=verify-full |
| MySQL, MariaDB, Aurora MySQL, PlanetScale | mysql://benchouse_read:PASSWORD@HOST:3306/DB?tls=true |
| SQL Server, Azure SQL | sqlserver://benchouse_read:PASSWORD@HOST:1433?database=DB&encrypt=true |
| ClickHouse | clickhouse://benchouse_read:PASSWORD@HOST:9440/DB?secure=true |
| Snowflake | snowflake://benchouse_read:TOKEN@ACCOUNT?warehouse=WH&role=BENCHOUSE_READ |
The connection must use TLS, and the host must be reachable from the internet. We store the connection string encrypted and never display it again.
The query
- Write one
SELECT. AWITHclause is fine; a second statement is rejected. - Return one row per turn, with the columns in the turns/v1 contract. Use
ASto rename your own columns. - Leave out
ORDER BYandLIMITat the top level. We add our own to page through the results. - On SQL Server, also leave out
WITH. Put that logic in a view and select from the view. - Selecting from a view works on every database:
SELECT * FROM benchouse.turns.
Connect as a user that can only read the tables the query uses. As a second
safeguard, we run the query in a read-only transaction on PostgreSQL and MySQL,
in a transaction that is always rolled back on SQL Server, and with readonly set on ClickHouse.
Testing the connection
Test connection runs the query without reading any rows. If a required column is missing or has the wrong type, the test fails and names the column.
Errors
| Problem | What happens |
|---|---|
| A required column is missing or has the wrong type | The run fails and the agent’s page names the column. Nothing is stored. |
| A row has NULL in a required column, or a value of the wrong type | The row is skipped and the rest are stored. We log the turn ID. |
| A row is larger than 4 MiB | The row is skipped. The result sample is usually the cause. |
| The query returns many rows | Each run reads up to 100,000 rows, or for up to 10 minutes, and the next run continues where it stopped. |
| A single query runs for more than 10 minutes | The run fails and is retried on the next run. |
Index turn_id and the column we read changes by (updated_at or asked_at).
Every run filters and sorts on them.