Repository navigation
v0.54.0 — a query time limit that holds
Fixed
resources.max_query_time_secondsis now enforced. It was declared, documented and rendered into the agent's system prompt, but nothing read it at execution time, so a contract could promise a 30 s limit while a query ran for as long as the database allowed.run_queryandpreview_tablenow stop a statement at the limit and answer with a blocked query that says which limit it hit and how to make the query lighter; inrun_queryit counts againstmax_retries. The message says whether the statement was cancelled or only abandoned, since the caller-side fallback cannot stop it. The limit does not cover Layer 2'sEXPLAINdry-run. With the field unset, behaviour is unchanged.contract_middlewarewraps a tool that runs its own query, so it cannot bound it; it now warns at wiring time when the field is set, as it already does fortoken_budget. (#114)
Changed
max_query_time_secondsmust be a finite number greater than zero; a contract setting it to0, a negative number or.infnow fails to load. Zero never meant "no limit" (omit the field for that), and under enforcement it would have meant opposite things: the caller-side timeout would block every query, while DuckDB drops an interrupt fired before its statement starts and would run with no limit at all.
Added
TimeoutAdapter, an optional adapter capability:execute_with_timeout(sql, timeout_seconds), raising the newQueryTimeoutError. It is detected at runtime, soDatabaseAdapteris unchanged. An adapter implementing it cancels the statement in the database. Any other adapter gets a caller-side timeout: the agent is answered on time but the statement keeps running, andcreate_toolswarns at wiring time, once per adapter class and limit.DuckDBAdapterimplements it withconnection.interrupt(), starting the clock only once the query holds the connection lock: the interrupt is connection-wide, so a clock that also counted the wait for the lock would cancel another caller's statement. After the deadline it keeps interrupting until the call returns, because DuckDB drops an interrupt sent before its statement starts, and recognises the timeout by the interrupt in the error's chain, so a subclass that re-raises it as another type still reports a timeout while a real engine error after the deadline (amemory_limitfailure, say) still reports itself. It runs the statement throughself.execute, so a subclass that rewrites SQL there — like a Denodo stand-in stripping VQL'sCONTEXTclause — keeps doing so under a time limit; the connection lock is now reentrant to allow it. On the caller-side path only the library's own deadline counts as the limit: a driver's ownTimeoutError(socket.timeout) is reported as the engine failure it is.
Internal
- Dependency refresh. The root lock takes 28 updates, notably
sqlglot30.18.0 -> 30.19.0 (Layer 1 static analysis),pydantic-ai-slim2.45.0 -> 2.51.0,anthropic1.6.0 -> 1.8.0,claude-agent-sdk0.2.156 -> 0.2.160,langchain-core1.6.3 -> 1.6.5 andlangsmith0.12.6 -> 0.14.1. No declared floor moves.experiments/mermaid-joinpath-evalis upgraded separately (openai3.3.1 -> 3.19.2; its 37 tests pass).experiments/dabstep-contract-evaltakes only the self version: its lock stays pinned for the paper's evaluation panel, and its frozen contract declares nomax_query_time_seconds, so this release does not change its behaviour. - Pre-commit hooks bumped (ruff and ty; the pinned revs live in
.pre-commit-config.yaml).
Full changelog: v0.53.0...v0.54.0