Official Python SDK for ValidAnytime: anytime-valid monitoring with budgeted page-tier alarms and clearly-labeled warn-tier early warnings.
Project description
validanytime
Official Python SDK for ValidAnytime — anytime-valid monitoring.
Alarms with a stated false-alarm budget, valid however often you look — plus unbudgeted early warnings, clearly labeled.
- Python >= 3.10, one runtime dependency (
httpx). - Sync (
Client) and async (AsyncClient) with identical method sets. - Typed dataclass models, actionable errors, automatic retries, idempotent ingestion.
pip install validanytime
Contents: Quickstart · Examples · The two alarm tiers · Autoconfig & the backtest gate · CLI · Configuration · Retries & idempotency · Errors
Quickstart
import validanytime
va = validanytime.Client() # reads VALIDANYTIME_API_KEY
# 1. Create a monitor from a template. The server resolves the template at
# create time and returns the RESOLVED config — what you see is what runs.
monitor = va.create_monitor(
"judge-scores",
template="llm_quality", # also: default, ml_drift, latency, kpi — each a
# distinct, gate-verified config (va.use_cases())
mu_baseline=0.92, # the level your metric should hold (optional:
) # omitted, it is learned from the first values)
# 2. Stream your metric.
va.ingest(monitor.id, 0.91, retry=True) # one event, retry-safe
va.ingest_batch(monitor.id, [0.90, 0.93, 0.92]) # or a batch
# 3. Read alarms — as often as you like.
for alarm in va.alarms(monitor.id):
print(alarm.tier, alarm.guarantee_tag, alarm.message)
Async is the same surface:
async with validanytime.AsyncClient() as va:
m = await va.create_monitor("judge-scores", template="llm_quality")
await va.ingest(m.id, 0.91, retry=True)
alarms = await va.alarms(m.id)
Examples
Runnable end-to-end scripts live in examples/:
quickstart.py— create, ingest, read both tiers.async_quickstart.py— the same flow, async.agent_autoconfig.py— shape-aware suggestion, ratified by the backtest gate before it goes live.ci_gate.py— a backtest gate that exits non-zero, so it drops straight into CI.
The two alarm tiers
Every alarm carries a guarantee_tag; Alarm.tier maps it to one of two tiers
(the same mapping the dashboard uses):
"page"— budgeted, anytime-valid. Page-tier alarms come from e-processes whose false-alarm control holds at every look at once, within a false-alarm budget stated up front. Pollingalarms()more often never degrades the guarantee. Tags:anytime_valid_under_conditional_mean_null,average_run_length_e_detector,online_evalue_fdr_control. Page-tier alarms carry the receipt:e_valueandtheorem_ref."warn"— early, unbudgeted, not guaranteed. Warn-tier alarms (tagheuristic_adaptive) come from classical detectors (e.g. CUSUM) whose calibration is model-based (iid standardized inputs) and not anytime-valid: on realistic data the false-warning rate can exceed the nominal rate substantially. Treat warnings as sensitive early hints; page humans on the page tier."unknown"— missing or unrecognized tag.
pages = [a for a in va.alarms(monitor.id) if a.tier == "page"]
warns = [a for a in va.alarms(monitor.id) if a.tier == "warn"]
Autoconfig & the backtest gate
Don't hand-write detector math. Bring the data; let the backtest gate — not any model — ratify a config before it goes live. This is the agent-driven path: your agent reads the metric (or asks the user), then calls the API.
sample = [...] # your metric's recent values
# Shape-aware suggestion, already run through the gate on your own history.
s = va.suggest_config(sample, history=sample) # -> Suggestion
if s.backtest.passed: # the non-bypassable check
m = va.create_monitor("my-metric", config=s.config)
print(s.backtest.summary) # "...caught it on day X."
va.use_cases()— the public use-case → config matrix, a data-free lookup (UseCaserows with the resolvedconfig+ adirection/rationaleyou can show the user). Pass a row'sconfigtocreate_monitor, or itsidasuse_case=tosuggest_configas a hint.va.suggest_config(sample, history=..., use_case=...)— reads the sample's shape (level, scale, direction, boundedness, drift), tunes a config, and ratifies it onhistory(defaults tosample). Returns aSuggestion(.config,.backtest). Check.backtest.passedbefore going live.va.backtest(history, config=..., inject_shift=...)— re-run the gate on any config. Graded on the page tier (.passed,.caught_at_seq); a warn-tier fire on the normal stretch shows up as.warned_on_normaland never fails the gate.inject_shift=Nonereplays as-is and only checks it stays quiet.
A backtest is a replay of past data, not a forecast about a different stream.
Other methods
-
get_monitor(id)/list_monitors()— the returnedconfigis always the resolved config that actually runs. -
update_monitor(id, name=..., template=... | config=...)— rename and/or reconfigure. A config change is validated like create and resets the monitor's derived engine state (carry, learned baseline, fired-alarm latches); the new config applies to future events only. The response'sstate_resetflag says whether that happened. -
list_events(id, after_seq=0, limit=500)— one seq-cursor page of the monitor's event history (ascending), as anEventsPageof typedEventrows (seq,value,ts,label) plusnext_after_seq(pass it back asafter_seq;Nonemeans done). -
iter_events(id)— a generator (async generator onAsyncClient) that walks the cursor transparently and yields everyEvent:for event in va.iter_events(monitor.id): print(event.seq, event.value)
va.fleet_status() returns the tenant's pooled certificate: fleet-confirmed
discoveries carry FDR <= alpha under arbitrary dependence between streams
(e-LOND). Warn-tier alarms never enter that pool.
CLI
Installing the package also installs a tiny validanytime command:
validanytime init # write a starter monitoring script (no network)
validanytime backtest metrics.csv # replay a CSV through the OFFLINE detector
# stack — `pip install 'validanytime[detectors]'`,
# runs locally, nothing leaves your machine
backtest prints the same machine-readable gate verdict the hosted
POST /v1/onboarding/backtest returns (add --json for the full dict) and
exits non-zero when the gate fails, so it drops straight into CI.
Configuration
| Setting | Argument | Environment variable | Default |
|---|---|---|---|
| API key | api_key |
VALIDANYTIME_API_KEY |
— (required) |
| Base URL | base_url |
VALIDANYTIME_API_URL |
https://api.validanytime.com |
Authentication is Authorization: Bearer va_... — mint keys from the
dashboard (the plaintext key is shown once).
Retries and idempotency
- The SDK retries requests that fail with 429, 502, 503, or 504 up to 3
times, with exponential backoff plus jitter; a
Retry-Afterheader (seconds) is honored. No other 4xx is ever retried. - Ingestion is idempotent per
event_id: the server deduplicates, so an event with anevent_idis safe to send twice — the second attempt returnsduplicate=True, accepted=0. Pass your ownevent_idwhen you have a natural one, or passretry=Truetoingest()and the SDK mints a uuid4event_idclient-side, so automatic retries (and your own re-sends of the same payload) can never double-count. - Ingesting without an
event_idis not deduplicated; a retried request could then count twice. Preferevent_id/retry=Truein production.
Errors
All SDK errors derive from validanytime.ValidAnytimeError and carry
status_code and the server's detail:
AuthError(401) — missing/invalid API key.NotFound(404) — unknown monitor (or another tenant's).ValidationError(422) — the server's message is designed to be actionable (e.g. it lists valid template ids or known config keys) and is surfaced verbatim.ServerError(5xx) — raised after retryable statuses are exhausted.
Development
The test suite is self-contained — it drives the client through
httpx.MockTransport, so no account, API key, or backend is required:
pip install -e '.[dev]'
pytest # tests
ruff check . # lint
ruff format --check . # formatting
pyright # types
See CONTRIBUTING.md for scope and pull-request guidelines.
License
Apache-2.0 © 2026 Compiled Intelligence, Inc. See NOTICE for attribution.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file validanytime-0.1.0.tar.gz.
File metadata
- Download URL: validanytime-0.1.0.tar.gz
- Upload date:
- Size: 39.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f4b0f7ade2f662e7844ce82ac0156b9b74d044640a1675487eb94eb59173334e
|
|
| MD5 |
3aca68bd7bdf2d2fc77a3726b7e8143c
|
|
| BLAKE2b-256 |
0007e1a1817d7f564f632a90383758d4e789d1c452e8581c54f3d8684adac3ba
|
File details
Details for the file validanytime-0.1.0-py3-none-any.whl.
File metadata
- Download URL: validanytime-0.1.0-py3-none-any.whl
- Upload date:
- Size: 29.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d2d7161bf047191ed1f8f91d7538a40c0b8102b100cde6e78946ea3490fbe363
|
|
| MD5 |
5dc4620b740918b6cc05aa8f8449a20a
|
|
| BLAKE2b-256 |
14f75fd0d4e5d508df2f0e8fb551554fab76459f2de37c7414f8591e1efdc189
|