v2.2.0
Added
POST /v1/oneshot/batch— one-shot batch endpoint. Loads (or references) an OBML model and runs N independent queries against it in a single round trip. Designed for agent workflows where one model + multiple sub-questions is the dominant pattern. Supportsmodel_yaml(transient or persisted viapersist_model: true) ormodel_id(reference an already-loaded model). Queries run concurrently under anasyncio.Semaphorecapped byONESHOT_BATCH_MAX_PARALLELISM(default 8). Per-query overrides fordialectandexecute. Stable result ordering keyed by caller-providedid. Partial failure is the default — each result carries its ownstatus(ok/error/cancelled) anderrorenvelope.fail_fast: truecancels remaining queries on first failure. Whole-batch and per-query timeouts are honored. Server limits surface viaGET /v1/settings.oneshot_batch. Seedesign/PLAN_oneshot_batch.md.- Model load deduplication.
POST /v1/sessions/{sid}/modelsandPOST /v1/oneshot/batchnow reuse an existingmodel_idwhen the same OBML bytes (whitespace-normalized) are already loaded in the session. The response includes a newmodel_loadfield ("fresh"|"reused"|"referenced"for batch). Skips parsing, validation, and OBSL graph generation on the dedup path. Disable withdedup: false. Index is per-session (session isolation preserved) and is cleared automatically on model removal. Seedesign/PLAN_model_load_dedup.md. - Freshness-driven result cache (file backend, off by default). New
CACHE_BACKEND=filemode persistsquery/executeresults in{CACHE_DIR}/meta.duckdb+ Parquet sidecars. TTL is derived from thefreshness:contract on each touched physicaldataObject— the minimum contribution wins, and an ETLPOST /v1/heartbeatinvalidates every cached query that depends on the refreshed table. Cachedquery/executeresponses gaincached,cached_at,ttl_seconds,ttl_source,ttl_limiting_table, andphysical_tablesfields. Per-query TTL caps viacaller_capped; unknown-freshness queries skip the cache by default (CACHE_UNKNOWN_FRESHNESS_POLICY=no_cache) or fall back to a default TTL when explicitly enabled. Fails closed: any cache error degrades to a normal warehouse execution. Seedocs/guide/result-cache.mdanddesign/PLAN_freshness_driven_cache.md. refresh:block on dataObjects (OBML + OSI). New per-dataObject freshness contract withmode: static | scheduled | heartbeat | unknown, plus mode-specific fields (schedule:cron,nextRefreshAt:,maxStaleness:,tolerance:). Roundtrips through OSI viaosi-obml/osi_obml_converter.py(custom_extensions) and is declared in the OBSL ontology (ontology/obsl.ttl). Surfaced inGET /v1/settingsand the Settings UI tab.GET /v1/cache/stats,POST /v1/cache/sweep,POST /v1/cache/clear. Stats exposes backend, entry count, total bytes, hit/miss counters, hit rate, oldest entry, next sweep, tracked physical tables, and heartbeat invalidations. Sweep runs one TTL + LRU capacity eviction pass on demand (returnsttl_evicted/capacity_evicted). Clear drops every entry regardless of TTL or dependencies (counters preserved as historical telemetry).POST /v1/heartbeat. ETL endpoint invalidating every cached query whose dependency set includes the refresheddatabase.schema.table. Bearer-auth viaHEARTBEAT_AUTH_TOKEN(route returns 404 when unset). Response listsinvalidated_cache_entriesandaffected_data_objects.- UI Cache panel. Settings tab now shows a side-by-side API Settings + Cache Stats view with Refresh Cache Stats, Sweep Cache now, and Clear Cache buttons. Auto-loads on tab open. Query Results tab annotates each execution with
(cache)or(database)next toexecution_time_ms.
Changed
execution_time_mson cache hits now reports the actual cache fetch + decode wall time, not the original DB run time persisted in the Parquet sidecar. Combined with thecached: trueflag, this gives realistic "from cache" durations on the wire. The original DB timing remains on disk for forensic inspection.CACHE_SWEEP_INTERVAL_SECONDSdefault raised from 900s (15 min) to 86400s (1 day). Lazy TTL on read keeps user-facing freshness correct; the periodic sweeper only matters for reclaiming disk from entries that expire without being read again, so every 15 min was unnecessarily aggressive.- Persisted cache state (
meta.duckdb+results/) is wiped on every server startup. Structural reason:model_idis regenerated as a fresh UUID on every model load, so any entries from a previous process run reference model_ids that no longer exist — orphans by construction. Starting empty avoids accumulating dead state between restarts. - Cache stats output now uses UTC for both
oldest_entryandnext_sweep_at. Previouslyoldest_entryrendered in the DuckDB session timezone (host local), creating a TZ mismatch withnext_sweep_at(always UTC). Both are now ISO 8601 with+00:00. - Startup logging splits the cache config block into one line per setting so each field is easy to grep without parsing one long line.
Fixed
- Gradio UI mounted in
create_app()always capturedquery_execute=Falsebecauseis_query_execute_enabled()reads a deps.py global that's only populated inside thelifespanhook (which runs after the UI is mounted). UI settings now resolve directly fromSettings, mirroring the same logic the lifespan uses, so the Execute Query button appears as soon asQUERY_EXECUTE=trueis set. - Query Results dataframe no longer renders Gradio's default
1, 2, 3placeholder columns before the first execution. The dataframe is hidden until the post-execute visibility chain flips it on.
Settings
- New env vars:
ONESHOT_BATCH_MAX_QUERIES(50),ONESHOT_BATCH_MAX_PARALLELISM(8),ONESHOT_BATCH_DEFAULT_TIMEOUT_MS(30000),ONESHOT_BATCH_BATCH_TIMEOUT_MS(120000). All exposed inGET /v1/settings.oneshot_batch. - New cache env vars:
CACHE_BACKEND(noopdefault),CACHE_DIR,CACHE_MIN_TTL_SECONDS(5),CACHE_MAX_TTL_SECONDS(86400),CACHE_MAX_VALUE_BYTES(10 MB),CACHE_MAX_DISK_BYTES(5 GB),CACHE_SWEEP_INTERVAL_SECONDS(86400),CACHE_UNKNOWN_FRESHNESS_POLICY(no_cache),CACHE_UNKNOWN_FRESHNESS_DEFAULT_TTL(300),HEARTBEAT_AUTH_TOKEN.
Behavior change
- Identical OBML loads in the same session now return the same
model_idinstead of minting a new one each time. Disable per-call withdedup: false. - When the cache is enabled (
CACHE_BACKEND=file),query/executecalls may serve cached results. Distinguish via the newcachedfield on the response. The cache fails closed on any error.
Full Changelog: v2.1.4...v2.2.0