Four gaps in hotdata-framework's HotdataClient, all surfaced while building the BM25 search tool (hotdata_langchain/search.py) and running it against production. Grouped because they all live in the same client surface.
Verified engine behaviour behind these: docs/engine-contract.md.
1. Errors discard the engine's message — highest impact
_execute_sql_once raises RuntimeError(e.reason or str(e)), which is the bare HTTP reason. The engine's actual message survives only in the underlying ApiException's body, further down the __cause__ chain.
What an agent sees vs. what the engine said:
agent sees : RuntimeError: Bad Request
engine said: Invalid function 'to_tsvector'. Did you mean 'to_char'?
This is not cosmetic. In a real agent run the model wrote Postgres full-text SQL, got Bad Request, and could not correct itself — the run aborted. The engine's own message is directly actionable and would very likely have been recovered from. Other observed examples that are similarly invisible today:
a database is required: set the X-Database-Id header or the database_id body field
No BM25 index found on column 'name' for <conn>.public.listings
must either specify a row count or at least one column
Fixing it here rather than per-consumer means hotdata-langchain, hotdata-ibis and hotdata-dlt-destination all benefit.
2. No create_index
There is no index API on HotdataClient at all, so the BM25 demo drops to raw hotdata.IndexesApi. Should cover both index types in one method — CreateIndexRequest already accepts bm25 and vector (fields: index_name, columns, index_type, metric, dimensions, embedding_provider_id, output_column, var_async) — rather than shipping a vector-only method and generalising later.
This blocks an agent provisioning its own searchable data: it can create a managed database and load into it, then cannot make it searchable.
3. resolve_managed_database falls back to name matching
It tries get_database(id), then scans list_databases() matching on db.name — which is a display label, not an identifier, and is not unique. Same bug class hotdata-dlt-destination removed entirely in its PR #59 ("address managed databases by id only, never by name").
Worse here than there, because names can arrive from an LLM: hotdata_load_managed_table(database="analytics", ...) could write into the wrong database on a collision. Latent rather than live — the test workspace has 8 databases with no duplicate labels.
4. from_env() picks a workspace silently
active = [w for w in workspaces if w.active]
chosen = active[0] if active else workspaces[0]
With HOTDATA_WORKSPACE unset there is no error and no warning. Given the tool set includes create_managed_database and load_managed_table, that is writes landing in a workspace nobody named. HotdataClient(api_key, workspace_id) already takes it explicitly; the ergonomic default is the problem.
Suggested order
(1) first — it is small and has demonstrated impact on agent behaviour. (2) unblocks self-provisioning. (3) and (4) are correctness/safety hardening and pair naturally with the id-first work in this repo.
Four gaps in
hotdata-framework'sHotdataClient, all surfaced while building the BM25 search tool (hotdata_langchain/search.py) and running it against production. Grouped because they all live in the same client surface.Verified engine behaviour behind these:
docs/engine-contract.md.1. Errors discard the engine's message — highest impact
_execute_sql_onceraisesRuntimeError(e.reason or str(e)), which is the bare HTTP reason. The engine's actual message survives only in the underlyingApiException'sbody, further down the__cause__chain.What an agent sees vs. what the engine said:
This is not cosmetic. In a real agent run the model wrote Postgres full-text SQL, got
Bad Request, and could not correct itself — the run aborted. The engine's own message is directly actionable and would very likely have been recovered from. Other observed examples that are similarly invisible today:a database is required: set the X-Database-Id header or the database_id body fieldNo BM25 index found on column 'name' for <conn>.public.listingsmust either specify a row count or at least one columnFixing it here rather than per-consumer means
hotdata-langchain,hotdata-ibisandhotdata-dlt-destinationall benefit.2. No
create_indexThere is no index API on
HotdataClientat all, so the BM25 demo drops to rawhotdata.IndexesApi. Should cover both index types in one method —CreateIndexRequestalready acceptsbm25andvector(fields:index_name,columns,index_type,metric,dimensions,embedding_provider_id,output_column,var_async) — rather than shipping a vector-only method and generalising later.This blocks an agent provisioning its own searchable data: it can create a managed database and load into it, then cannot make it searchable.
3.
resolve_managed_databasefalls back to name matchingIt tries
get_database(id), then scanslist_databases()matching ondb.name— which is a display label, not an identifier, and is not unique. Same bug classhotdata-dlt-destinationremoved entirely in its PR #59 ("address managed databases by id only, never by name").Worse here than there, because names can arrive from an LLM:
hotdata_load_managed_table(database="analytics", ...)could write into the wrong database on a collision. Latent rather than live — the test workspace has 8 databases with no duplicate labels.4.
from_env()picks a workspace silentlyWith
HOTDATA_WORKSPACEunset there is no error and no warning. Given the tool set includescreate_managed_databaseandload_managed_table, that is writes landing in a workspace nobody named.HotdataClient(api_key, workspace_id)already takes it explicitly; the ergonomic default is the problem.Suggested order
(1) first — it is small and has demonstrated impact on agent behaviour. (2) unblocks self-provisioning. (3) and (4) are correctness/safety hardening and pair naturally with the id-first work in this repo.