Omen - An Agentic Time Series Forecasting Platform
Status: Alpha. The test suite (161 tests) is real and the book's
worked examples were checked against live tool output. CI (GitHub
Actions, see .github/workflows/ci.yml) runs the suite plus ruff
linting and mypy type-checking on every push and PR; see "Publishing
this to PyPI" below for the rest of what's still open.
Five layers of agentic time series tooling, packaged as a normal installable Python project. Each layer is a FastMCP server (typed tools) plus a companion OpenClaw skill (the reasoning workflow around those tools), bundled together so the whole thing installs and updates as one package.
- Layer 1 —
ts-analyst: explore a series (stationarity, seasonality, anomalies, structural breaks) and recommend a forecasting approach with reasoning. - Layer 2 —
ts-forecaster: fit candidate models against a common held-out window, backtest them (optionally across multiple rolling origins), compare them statistically, and recommend one with reasoning grounded in real error metrics and residual diagnostics. - Layer 3 —
ts-deploy: retrain the chosen model on the full series and produce a real forecast beyond the end of the data, with prediction intervals where available (now including gradient-boosted trees, via quantile regression), an automated plausibility check against the series' own history, and an optional weighted ensemble across multiple candidates. - Layer 4 —
ts-monitor: once real observations exist, check whether the deployed forecast is still tracking reality (now with a bootstrap confidence interval on its error metrics), detect data drift (now with an effect size, not just a bare p-value), and recommend whether to retrain -- flagging when that recommendation is itself close to the threshold rather than clear-cut. - Layer 5 —
ts-retrain: whents-monitorsaysretrain_now, re-run Layers 1-2 on the updated series and deterministically decide whether the freshly backtested candidate beats what's currently deployed by enough to be worth redeploying (now confidence-interval- aware on both sides of that comparison, not just bare point estimates). Never redeploys without an explicitconfirmed=True-- by default that means stopping for human confirmation, and the optional autonomous-mode alternative is now backed by a real, code-checked authorization record rather than only a prose contract.
Layers 1, 2, 3, and 4 also expose 13 plot_* tools between them (none
in Layer 5 -- a redeploy verdict has no natural chart) that render a
matplotlib figure and return it as an inline image in the same turn,
with an optional out_path to also save a PNG to disk. These are
strictly supplementary visual feedback: every plot re-derives its
picture from the same real computation its JSON counterpart already
returns, and never reports a finding the JSON tool doesn't already
report on its own. See "Things worth knowing about specific tools"
below for the mechanics.
Learn more
book/-- Agentic Time Series Forecasting for Supervillains, a complete 22-chapter e-book (plus glossary, tool reference, and further-reading appendices) teaching time series forecasting through this toolkit, one concept per chapter, each worked example run for real against the live MCP servers rather than hand-typed.book/examples/generate_book_datasets.pyandgenerate_book_plots.pyregenerate every dataset and every plot image the book uses, deterministically, so its numbers -- and its pictures -- are reproducible rather than just asserted.assemble_book.pyin the same directory concatenates the front matter, all 22 chapters, and the three appendices into a single Markdown file and (via pandoc) a PDF. Start atbook/outline.mdor jump straight tobook/chapter-01-*.md.blog-posts/-- shorter, punchier write-ups covering the same five layers, one post per layer plus an introductory overview.prompts/testing-and-learning-prompts.md-- ready-to-use prompts across all five layers for hands-on practice with your own data.
Project layout
omen/
├── pyproject.toml
├── LICENSE
├── README.md
├── AGENTS.md
├── .gitignore
├── openclaw.config.snippet.jsonc
├── src/
│ └── omen/
│ ├── __init__.py # version + skills_dir() helper
│ ├── data_prep.py # shared: synthetic data + CSV loader (used by all 5 layers)
│ ├── plotting.py # shared: render_plot() -- inline Image + optional PNG, used by all plot_* tools
│ ├── analyst/
│ │ ├── __init__.py
│ │ ├── analysis_tools.py # Layer 1 diagnostic functions
│ │ ├── plot_tools.py # Layer 1 plotting functions (6 tools)
│ │ └── server.py # FastMCP server: ts-analyst
│ ├── forecaster/
│ │ ├── __init__.py
│ │ ├── model_tools.py # Layer 2 fit/backtest functions
│ │ ├── plot_tools.py # Layer 2 plotting functions (3 tools)
│ │ └── server.py # FastMCP server: ts-forecaster
│ ├── deploy/
│ │ ├── __init__.py
│ │ ├── forecast_tools.py # Layer 3 retrain/forecast functions
│ │ ├── plot_tools.py # Layer 3 plotting functions (1 tool)
│ │ └── server.py # FastMCP server: ts-deploy
│ ├── monitor/
│ │ ├── __init__.py
│ │ ├── monitor_tools.py # Layer 4 comparison/drift/retrain-decision functions
│ │ ├── plot_tools.py # Layer 4 plotting functions (3 tools)
│ │ └── server.py # FastMCP server: ts-monitor
│ ├── retrain/
│ │ ├── __init__.py
│ │ ├── retrain_tools.py # Layer 5 deployment-manifest + redeploy-decision functions
│ │ └── server.py # FastMCP server: ts-retrain (no plotting tools -- see below)
│ └── skills/ # bundled as package data -- see skills_dir()
│ ├── ts-analyst/SKILL.md
│ ├── ts-forecaster/SKILL.md
│ ├── ts-deploy/SKILL.md
│ ├── ts-monitor/SKILL.md
│ └── ts-retrain/SKILL.md
├── tests/
│ ├── test_data_prep.py
│ ├── test_analyst_tools.py
│ ├── test_analyst_plot_tools.py
│ ├── test_forecaster_tools.py
│ ├── test_forecaster_plot_tools.py
│ ├── test_deploy_tools.py
│ ├── test_deploy_plot_tools.py
│ ├── test_monitor_tools.py
│ ├── test_monitor_plot_tools.py
│ └── test_retrain_tools.py
├── blog-posts/ # draft write-ups about this project, not part of the package
│ ├── introducing-omen.md
│ ├── ts-analyst-gets-a-statistics-degree.md
│ ├── ts-forecaster-shows-its-work.md
│ ├── ts-deploy-ships-it.md
│ ├── ts-monitor-learns-to-doubt-good-news.md
│ └── ts-retrain-checks-its-papers.md
├── prompts/ # ready-to-use prompts for testing/learning each layer
│ └── testing-and-learning-prompts.md
└── book/ # "Agentic Time Series Forecasting for Supervillains" e-book, not part of the package
├── dedication.md, about_the_author.md
├── outline.md
├── chapter-01-introducing-omen-and-agentic-ai.md ... chapter-22-conclusion.md
├── appendix-a-glossary.md, appendix-b-tool-reference.md, appendix-c-further-reading.md
└── examples/
├── generate_book_datasets.py # regenerates every dataset the book uses
├── generate_book_plots.py # regenerates every plot image the book embeds
├── assemble_book.py # concatenates the book into one Markdown file + PDF
└── images/ # the 32 real PNGs embedded in the book's chapters
What changed from the earlier ad-hoc layout
- One
data_prep.py, not four copies. Every layer previously had its own duplicate for self-containment as a standalone MCP server folder; now they all importomen.data_prep. - Real packaging metadata.
pyproject.tomldeclares dependencies, optional extras per layer, and console-script entry points (ts-analyst-server,ts-forecaster-server,ts-deploy-server,ts-monitor-server) so OpenClaw's config can reference an installed command instead of an absolute path to a.pyfile. - Skills are bundled package data.
omen.skills_dir()returns the installed path to the fourSKILL.mdfiles, so you can install this package and copy the skills into an OpenClaw workspace without needing the original source tree around. - A real test suite, using
pytest.importorskipfor the tests that needstatsmodels/scikit-learn, sopip install -e .(core deps only) still lets you run the tests that don't need them.
Setup
1. Install
python -m venv .venv && source .venv/bin/activate # optional but recommended
pip install -e ".[all]" # every layer's dependencies
# or install only what you need:
# pip install -e ".[analyst]" # Layer 1 only
# pip install -e ".[forecaster,deploy]" # Layers 2+3
# pip install -e ".[monitor]" # Layer 4 only
# pip install -e ".[retrain]" # Layer 5 only (no extra deps beyond core)
# pip install -e ".[dev]" # + pytest, for running tests
2. Run the test suite
pip install -e ".[all,dev]"
pytest
3. Sanity-check each server runs standalone
ts-analyst-server # Ctrl+C to stop; no output/crash = good
ts-forecaster-server
ts-deploy-server
ts-monitor-server
ts-retrain-server
4. Register with OpenClaw
Merge openclaw.config.snippet.jsonc into ~/.openclaw/openclaw.json --
no path editing needed if the console scripts are on PATH (true inside
the venv you installed into). Then:
openclaw mcp status --verbose
openclaw mcp doctor --probe
openclaw mcp tools ts-analyst
5. Install the bundled skills
mkdir -p ~/.openclaw/workspace/skills
cp -r "$(python -c 'import omen as t; print(t.skills_dir())')"/* \
~/.openclaw/workspace/skills/
Start a new OpenClaw session afterward (skills are snapshotted at session start).
6. Point OpenClaw at your model of choice
This project was developed against GLM-5.2 on Ollama Cloud:
export OLLAMA_API_KEY="<your-ollama-cloud-api-key>"
openclaw models list --provider ollama-cloud
openclaw models set ollama-cloud/glm-5.2:cloud
Nothing about the package is tied to that specific model -- swap
agents.defaults.model.primary in the config for whatever you're running.
Run it
Full pipeline, one message to your OpenClaw agent:
Use ts-analyst to explore a synthetic time series, then use ts-forecaster to fit and backtest candidate models informed by what you found, then use ts-deploy to produce a 30-day forecast with the best-performing model and settings.
Once time has passed and the CSV has real new observations:
Use ts-monitor to check whether that forecast is holding up against what actually happened, and tell me if I should retrain.
Right after that first real ts-deploy call, record what got deployed so
Layer 5 has a baseline to compare against later:
Use ts-retrain to record that this model and its backtest metrics are now deployed.
If ts-monitor comes back with retrain_now:
Use ts-retrain to re-run analyst and forecaster on the updated series and tell me whether the new candidate is actually worth redeploying.
That call stops at the verdict -- ts-retrain never redeploys on its own
in the default mode. If the verdict says should_redeploy: true and you
want to proceed, confirm it explicitly in a follow-up message:
Go ahead and redeploy the candidate you just recommended.
which is what actually triggers ts-retrain__execute_redeploy(..., confirmed=True) and updates the manifest.
If you'd rather not be asked each time for a specific series, opt into
autonomous mode explicitly -- e.g. as a standing instruction in this
project's own AGENTS.md, or stated up front in the conversation:
For the
daily_demand.csvseries specifically, you're authorized to redeploy automatically whenever ts-retrain finds a candidate that beats the current deployment -- no need to ask me first. Everything else still needs my confirmation as usual.
That grant is what the agent should turn into a persisted record via
ts-retrain__authorize_autonomous_mode(csv_path="daily_demand.csv", authorized_by="user, in conversation") -- not just remember for the rest
of the session. With that record in place, a later retrain_now cycle
for that series will call execute_redeploy(confirmed=True, autonomous=True) itself once should_redeploy: true comes back (which
itself re-checks the record before acting), and report what it did rather
than pausing to ask.
Publishing this to PyPI
This layout is ready for it as-is:
pip install build twine
python -m build # produces dist/*.whl and dist/*.tar.gz
twine upload --repository testpypi dist/* # try TestPyPI first
twine upload dist/* # then the real thing
Before actually publishing, you'll want to:
confirmDone.omenis actually free on PyPIomenitself was already taken -- the plain word is short and generic, exactly the kind of name that gets claimed early. The project now publishes underomen-agentic-forecastinginstead (confirmed free via PyPI's JSON API), set inpyproject.toml'snamefield. The importable module stays plainomen-- only the PyPI listing name changed; see the comment above[project.scripts]inpyproject.tomlfor why.fill in real author info inDone.pyproject.tomlauthorsnow lists the real name/email instead of the"Your Name" <you@example.com>placeholder.- bump
versionfor each release
Things worth knowing about specific tools (carried over from earlier layers)
-
The 13
plot_*tools (ts-analyst:plot_series,plot_acf_pacf,plot_seasonal_decomposition,plot_periodogram,plot_anomalies,plot_changepoints;ts-forecaster:plot_backtest,plot_rolling_origin,plot_search_sarima_orders;ts-deploy:plot_forecast;ts-monitor:plot_forecast_vs_actuals,plot_drift,plot_rolling_drift) all share one rendering path,omen.plotting.render_plot(). Every call returns aToolResultcombining an inline FastMCPImagecontent block (base64 PNG, rendered in the same turn a client supports it, e.g. Claude Desktop) with a smallstructured_contentdict (status,written_to, plus whatever else that specific plot wants to surface, e.g.n_anomalies_flagged). Passout_pathto also write the PNG to disk; omit it and you still get the inline image, just no file on disk.matplotlibis a core dependency (not a per-layer extra) specifically so every layer can offer this without an opt-in install step. Every plot function calls its corresponding JSON tool's own computation internally (e.g.plot_driftcallsdetect_data_drift,plot_acf_pacfreusesacf_pacf_summary's own Bartlett-band math) rather than reimplementing the statistics -- a picture can never silently disagree with the numbers behind it.ts-retraindeliberately has none: a redeploy verdict is a single threshold comparison, not something a chart adds value to. Seebook/examples/generate_book_plots.pyfor 16 real, reproducible worked examples across the book's chapters. -
ts-analyst__check_stationarityruns both ADF and KPSS and combines them into one joint verdict, each with its own effect size AND confidence interval. ADF's null is a unit root; KPSS's null is stationarity -- opposite nulls, so running both and readinginterpretation's four-way readout (agree stationary / agree non-stationary / disagree in either direction) is standard practice, not redundant.adf_p_value/adf_is_likely_stationarycome with a mean-reversion effect size (mean_reversion_lambda,mean_reversion_half_life_periods) and its confidence interval (mean_reversion_lambda_ci_lower/upper,mean_reversion_half_life_ci_lower/upper) -- a series can clearp < 0.05while reverting so slowly the half-life is impractically long for a short-horizon forecast, so check both, not just the p-value; the CI additionally shows how precisely that half-life is actually known.mean_reversion_half_life_ci_upperisnull(unbounded) whenever lambda's own CI reaches non-negative territory -- the data can't rule out arbitrarily slow reversion at that end. Don't read a small positive (or slightly negative-but-near-zero)mean_reversion_lambdaas proof of "no reversion" on its own -- under a true unit root, this OLS estimate is known to skew slightly negative in finite samples;adf_is_likely_stationaryand the half-life's magnitude are the more reliable signals.kpss_p_value/kpss_is_likely_stationarycome withkpss_effect_size(the statistic as a multiple of its 5% critical value) -- KPSS's own p-value is clipped at lookup-table boundaries, so the effect size is what actually distinguishes a borderline result from a wildly non-stationary one once the p-value is pinned at 0.01 or 0.10. Note the field names changed from the tool's original single-test version (p_value/is_likely_stationaryare nowadf_p_value/adf_is_likely_stationary). -
ts-analyst__basic_statsreports a confidence interval for the mean (mean_ci_lower,mean_ci_upper, Student's t, default 95% viaconfidence_level) -- both arenullfor a constant series (zero variance, no interval to report). -
ts-analyst__acf_pacf_summaryandts-analyst__detect_anomalies_zscoreboth report an effect size for anything they flag, not just a bare pass/fail.acf_pacf_summarynow uses statsmodels' Bartlett-formula PER-LAG confidence intervals to decide significance, not a single global threshold -- the correct standard error for ACF grows with lag (it depends on the cumulative autocorrelation of earlier lags), so a uniform1.96/sqrt(n)threshold (an earlier version of this tool) is only actually correct at lag 1 and understates the true threshold at later lags.significant_acf_lagsis a list of{lag, acf, ci_lower, ci_upper, effect_size}entries (effect_size= ACF magnitude as a multiple of that lag's OWN interval half-width), sorted strongest first and capped at 10 -- on the project's synthetic data, this correctly ranks lag 1 as the single strongest entry (its Bartlett SE is the tightest, with no prior lags inflating it), where the old uniform-threshold version incorrectly ranked lag 7 first.detect_anomalies_zscore'sanomaliesis a list of{date, value, z_score}entries, sorted most extreme first and capped at 15, plus amax_abs_z_scoresummary. Both fields changed shape from earlier versions --significant_acf_lagswas a bare list of lag integers (with a singlesignificance_threshold, nowsignificance_alpha), anddetect_anomalies_zscorereturnedanomaly_dates(date strings only, no magnitude) instead ofanomalies. -
ts-analyst__detect_seasonality_periodfinds a candidate seasonal period FOR you (via periodogram + Fisher's g-test), rather than requiring you to already know one before callingseasonal_decomposition_summaryoracf_pacf_summary. The significance test applies to the single globally strongest frequency in the FULL periodogram, which can correspond to a period outside the reported[min_period, max_period]range (commonly the series' own trend, at a period near its full length) -- always checkdominant_period_in_reported_rangebefore treating the significance test as endorsing one oftop_candidate_periodsspecifically. The p-value uses the standard conservative upper-bound approximation for Fisher's g-test, not the full alternating-series formula. -
ts-analyst__detect_anomalies_robust_zscoreexists because the originaldetect_anomalies_zscorehas a real, confirmed weakness: its rolling window's own std is inflated by the very anomaly it's trying to measure (a +500 spike on a ~200-scale series only scored z=3.44, not something far higher). The robust version uses a rolling median + MAD (modified z-score, Iglewicz & Hoya 1993) instead, which isn't self-diluted the same way -- confirmed on the same spike, it scores 17.26. Defaultz_thresholdis 3.5, not 3.0 (the literature-recommended default for the modified z-score specifically). Neither tool replaces the other -- reach for the robust version when you suspect self-dilution might be masking a real anomaly. -
ts-analyst__detect_changepointsflags a lasting shift in the series' MEAN LEVEL, not a point anomaly -- a different job from eitherdetect_anomalies_zscorevariant above. Uses binary segmentation with a CUSUM statistic and a permutation test (deterministic given the sameseed, default 42). Each changepoint reports Cohen's d as an effect size. Known limitation, not a bug: binary segmentation's per-split p-values are local tests within whatever segment existed at that point in the recursion -- there's no exact global significance guarantee for the full set of changepoints reported. This is a standard, accepted tradeoff for this class of algorithm; treatalpha/max_changepointsas tuning knobs, not as controlling an exact false-discovery rate. -
Every
ts-forecasterfit_*tool'sbacktest_metricsnow includes a bootstrap confidence interval (mae_ci_lower/upper, etc., percentile method, deterministic givenseed, default 1000 resamples) -- a backtest metric computed over a modestholdout_size(often ~30 points) has real sampling uncertainty of its own; two models scoring MAPE 4.8% and 5.0% might not be a meaningfully different result once you see how wide each one's own interval is. Each result also now includesholdout_actuals/holdout_predictedarrays, andresidual_diagnostics(ETS/SARIMA) reportsljung_box_effect_size(the Q statistic as a multiple of its own critical value) alongside the p-value. -
ts-forecaster__diebold_mariano_testgives model comparison actual statistical backing. Previously "compare candidates honestly" meant eyeballing two error numbers with no way to tell a real difference from holdout noise. This runs a Diebold-Mariano-style test (1995) on two models' paired forecast errors from the SAME holdout (passholdout_actualsand each model'sholdout_predicted), using a Newey-West/Bartlett-kernel HAC-robust variance estimate (automatic lag selection by default) and a Student's t reference distribution for small-sample conservatism. Returnsis_significant_differenceandfavored_model(nullif not significant). Passn_lags=0when comparing two one-step-ahead backtests specifically (e.g. twofit_gradient_boosted_treesruns) -- the default automatic lag selection assumes genuinely multi-step, autocorrelated forecast errors. This test tells you whether two models' error numbers differ significantly; it does NOT resolve the one-step-ahead-vs-recursive evaluation mismatch below when comparing across that boundary. -
fit_ets/fit_sarimanow also reportaicc(small-sample-corrected AIC, Hurvich & Tsai 1989 --Nonewhen the training size is too small relative to the parameter count for the correction to make sense) andbacktest_interval_coverage-- a prediction interval built during the backtest (simulated for ETS, analytic for SARIMA) checked against the REAL holdout values, using the exact same coverage-check shape/logic (and 15-percentage-pointwell_calibratedthreshold) asts-monitor__compare_forecast_to_actuals. This catches a badly calibrated interval during backtesting, before it's ever deployed, instead of waiting forts-monitorto notice after the fact. -
ts-forecaster__rolling_origin_backtestaddresses a real limitation every other tool in this layer still has: everyfit_*call evaluates against a single, arbitrarily-chosen fixed holdout window -- even the bootstrap CI above only resamples points within that one window, so a single unlucky/lucky holdout period still biases everything computed from it. This repeatsfit_ets/fit_sarima/fit_gradient_boosted_trees(notfit_naive_baselines-- naive baselines don't need walk-forward rigor) at multiple non-overlapping origins with an expanding training window, and reports the mean/std of backtest MAE/RMSE/MAPE across origins -- a large std relative to the mean is a genuine, direct measure that a model's apparent edge isn't stable across different stretches of the series. Costsn_originstimes a single fit, since each origin genuinely refits the model. -
ts-forecaster__search_sarima_ordersis an advisory grid search, not an authority. It searches(p,q)(P,Q)combinations (withd/seasonal_dheld fixed -- pass them explicitly, informed byts-analyst's stationarity findings, not searched) ranked by AICc, reusingfit_sarimafor every candidate so the fitting logic isn't duplicated. This is deliberately scoped to not replace the project's existing "the agent reasons about settings from Layer 1 findings" design (seets-forecaster/SKILL.md) -- verified directly on the project's own synthetic data that the numerically-best-AICc candidate can still haveresiduals_look_like_white_noise: false, i.e. a candidate this search ranks first can still be a worse choice by other criteria the agent needs to check regardless. Bounded bymax_combinations(default 60) to avoid runaway compute. -
ts-forecaster's gradient-boosted-trees backtest is one-step-ahead (uses true lagged values), while ETS/SARIMA get scored on a genuine multi-step forecast -- not directly comparable without accounting for that. -
ts-deploy__forecast_naivenow has an analytic prediction interval too -- previously the onlyforecast_*tool with zero interval capability, ever. Built from this same naive method's own in-sample residual standard deviation (one-step differences for flat naive, seasonal differences for seasonal naive) and widening withsqrt(elapsed steps/cycles)-- the standard textbook interval for a random-walk-style forecast (Hyndman & Athanasopoulos), not simulation- based. Falls back to point-forecast-only (seeinterval_note) if there's fewer than 2 residuals to estimate a standard deviation from (i.e. a 2-row series or shorter for flat naive). As a side effect,forecast_ensemblecombinations that include"naive"can now get a combined interval too, where before they never could. -
ts-deploy's gradient-boosted-trees forecast is recursive (each prediction feeds back in as a lag for the next step), so errors can compound over a long horizon -- a risk that didn't apply to Layer 2's evaluation of the same model type. It now also has a prediction interval, via two extraGradientBoostingRegressormodels trained withloss="quantile"alongside the point model -- an approximate interval that does NOT itself grow with the recursive compounding risk above, unlike SARIMA's analytic interval or ETS's simulated one (interval_note/caveatboth say so explicitly). Recursive lag features always follow the POINT model's own trajectory, never the quantile models' -- one consistent path instead of three diverging ones. Independently-fit quantile models can cross (lower > upper); guarded against with an elementwise min/max before returning.feature_importancesnow includes a bootstrap confidence interval per feature ({col: {importance, ci_lower, ci_upper}}-- a shape change, not just an added field). Refits the point model onn_bootstrap(default 100) resamples of the TRAINING ROWS, not resampled errors -- there's no "error" to resample for a feature-importance question, only what the model was fit on. Real extra cost:n_bootstrapfull model refits on top of the three already needed for the forecast/interval. Confirmed on the project's own synthetic data that this CI is informative, not decorative:lag_7(the real weekly seasonality) showedimportance=0.593butci_lower=0.26, ci_upper=0.77-- a genuinely wide range, sincelag_7andlag_14compete for "explaining" the same weekly pattern and which one wins varies by resample.n_bootstrap=0skips this cheaply (ci_lower/ci_uppercome backnull) --forecast_ensemble's internal GBT fit always uses this, since ensemble results never surfacefeature_importancesat all. -
Every
ts-deploy__forecast_*tool (includingforecast_ensemble) returns aplausibility_checkfield that automates part of the "does this look plausible" eyeball checkts-deploy/SKILL.mdStep 3 otherwise leaves entirely to the agent. It compares the forecast's implied endpoint change against the empirical distribution of horizon-length changes the series has actually made historically (endpoint_change_z_score,endpoint_change_percentile_rank,is_extreme_relative_to_history), and separately flagsgoes_below_historical_min/goes_above_historical_max. This is NOT a hypothesis test and not a verdict -- there's no null distribution being tested, only "has the series done something like this before"; a genuine regime change can legitimately produce a flagged forecast that's still correct.nullfields when there's not enough history (n <= horizon) to compute the comparison at all. -
ts-deploy__forecast_ensemblecombines two or more candidates into one weighted forecast -- the tool for "what do I actually deploy" when Layer 2 leaves more than one reasonable candidate, filling the gap thets-forecasterblog post flagged as a Next Step (combining candidates is this layer's job, evaluating them individually is Layer 2's).weightsdefault to equal, needn't be pre-normalized (raw inverse-MAE values from a Layer 2 comparison work directly), and the combined point forecast is a straightforward weighted average at each date. The combined interval, reported only when EVERY included model contributes one of its own, is a VARIANCE combination, not a bound average: each component's own interval width is converted to an implied standard deviation, combined viasqrt(sum(w_i^2 * sigma_i^2))assuming the components' errors are INDEPENDENT, then rebuilt around the weighted point forecast. More principled than literally averaging bounds -- but the independence assumption is optimistic (every component is fit on the SAME series and shares real error structure), sointerval_noteframes the result as a lower bound on the ensemble's true uncertainty, not a precise one. Confirmed on the project's own synthetic data: two identical naive components at equal weight (0.5/0.5) combine to a 95% interval exactly1/sqrt(2)(≈0.707x) the width of either component's own interval alone -- narrower than any single component, which is the expected mathematical effect of combining independent estimates, not a bug. Including"gbt"carries its recursive- compounding caveat into the combined result too (diluted by weight, not eliminated). -
ts-monitor's drift detector can't distinguish trend/seasonality from a genuine regime change -- confirmed directly: running it on this project's synthetic data (which has a real upward trend) flagsdrift_detected=Trueeven with no injected anomaly, purely from trend continuation. Read theinterpretationfield rather than treating the boolean as an automatic alarm. It now reports a magnitude alongside that boolean, not just a bare p-value:mean_shift_cohens_d(pooled- SD effect size for the mean shift) plus the rawttest_statistic/ks_statisticthe two tests are actually built on -- previously computed internally and silently dropped before returning, a real oversight fixed alongside the effect-size addition. Confirmed on the project's own trending synthetic data: the unmodified series' trend- driven "drift" comes back withmean_shift_cohens_d≈-0.42, while injecting an obvious +200 level shift on top of it pushes that to≈7.06-- both flaggeddrift_detected=True, but only the effect size tells you which one is a trend continuation and which one is a wall. -
ts-monitor__rolling_drift_checkaddresses the same "one arbitrary window" fragilityts-forecaster__rolling_origin_backtestfixed for backtesting, applied to drift detection. A singledetect_data_driftcall only compares one recent window against one reference window -- it can't tell you whether a flagged shift is a sustained pattern or an isolated blip. This repeats the check atn_checks(default 5) non-overlapping points walking backward through the series and reportspersistent_drift: truewhen at leastpersistence_threshold_frac(default 50%) of them flag drift. Unlikerolling_origin_backtest, this is cheap per check (a t-test and KS test on numpy arrays, no model fitting), so a largern_checkscosts little. Confirmed on the project's own trending synthetic data: all 5 rolling checks flag drift (frac_flagged=1.0), correctly identifying the ongoing trend as a sustained pattern rather than a one-off blip -- consistent withdetect_data_drift's own documented false-positive-on-trend caveat. -
ts-monitor__compare_forecast_to_actuals'sbacktest_style_metricsnow includes a bootstrap confidence interval (mae_ci_lower/upper, etc., same percentile-bootstrap technique and field names asts-forecaster's backtest metrics) -- a comparison drawn from just a handful of elapsed forecast dates has real sampling uncertainty of its own, arguably more consequential here than in a Layer 2 backtest since it's about real post-deployment performance, not a held-out window. -
interval_coveragenow also reports a Wilson score confidence interval on the coverage percentage itself (empirical_coverage_ci_lower/upper) -- coverage is a proportion computed from however many dates have elapsed so far, often a small handful, so "100% coverage" from 10 points is far less reassuring than it sounds. Confirmed directly: 10/10 matched points inside their interval givesempirical_coverage_pct=100.0but a Wilson CI of[72.25%, 100.0%]-- genuinely wide. This is supplementary information only and deliberately does NOT changewell_calibrated's existing 15-percentage-point threshold verdict, which stays intentionally identical tots-forecaster's mirroredbacktest_interval_coveragecheck (seeAGENTS.md). -
compare_forecast_to_actualsnow flags residual outliers among the matched-point comparisons, using the same modified-z-score (MAD-based) technique asts-analyst__detect_anomalies_robust_zscore, and reportsmetrics_excluding_outliers-- letting the caller distinguish "the forecast missed by a little every day" from "the forecast was fine except for one wild day (a promotion, an outage)," which call for different responses when deciding whether the MODEL itself needs retraining. Known limitation, not a bug: if half or more of the residuals are exactly identical (most realistically exactly 0), the MAD degenerates to 0 and every z-score collapses to 0, potentially masking a genuine outlier -- the same self-dilution failure classts-analyst's original (non-robust) anomaly detector had, just triggered by a rarer condition. -
ts-monitor__recommend_retrainingis deliberately deterministic rather than left to model judgment -- "should we retrain" is the kind of decision worth being reproducible given the same inputs. It can now optionally acceptmape_now_ci_lower/mape_now_ci_upper(from the bootstrap CI above) and will reportpct_degradation_ci_lower/upperplus flagdegradation_threshold_within_ci: truewhen the degradation threshold itself falls inside that range -- i.e. the degraded/ not-degraded verdict is sensitive to sampling noise inmape_now, not a clean call, and the tool says so inreasoningrather than reporting a falsely confident point-estimate verdict. -
ts-retrainonly ever changes the deployment through one gated tool,ts-retrain__execute_redeploy-- it re-runs Layers 1-2, hands the resulting candidate tots-retrain__compare_candidate_to_deployed(deterministic, same reason asrecommend_retrainingabove), andexecute_redeployitself refuses to do anything unless called withconfirmed=True. There is no default that takes action. -
ts-retrain__compare_candidate_to_deployednow uses confidence-interval data it was already being handed but previously ignored. The fullbacktest_metricsdicts passed in ascandidate_metrics/deployed_metricsalready carry a bootstrap CI for the compared metric (fromts-forecaster'scompute_metrics_with_ci) whenever the candidate came from a recentfit_*call -- the function just wasn't reading it. It now reportspct_improvement_ci_lower/upper(the implied improvement range) and flagsredeploy_threshold_within_ci: truewhenimprovement_threshold_pctfalls inside that range -- meaningshould_redeployis close to a coin flip on backtest sampling noise. Ifdeployed_metricsalso carries a CI, its uncertainty gets combined in too (deployed_metrics_ci_used: true), via proper interval arithmetic over both ranges at once rather than a "one side treated as fixed" simplification -- confirmed directly: candidate MAPE 6.0% (CI[5.0, 7.0]) against deployed MAPE 10.0% alone implies[30.0%, 50.0%]; adding a deployed-side CI of[9.0, 11.0]widens that to[22.22%, 54.55%], exactly as worst-case/best-case reasoning over both ranges predicts (worst case: candidate at its highest paired with deployed at its lowest; best case: the reverse). Falls back to the old fixed-deployed-value behavior automatically wheneverdeployed_metricshas no CI for the metric (deployed_metrics_ci_used: false) -- e.g. an older manifest recorded before this session's CI work. -
ts-retrain__execute_redeploynow returnsprevious_deployment-- whatever the manifest held immediately before this call overwrote it (ornullon a genuinely first deployment), read before the write happens rather than reconstructed afterward. The manifest file itself is unchanged and still only ever holds the single current deployment, not a history -- this is a one-time snapshot in the ACTION'S OWN output, so "what did this redeploy actually replace" is answerable straight from the tool call instead of requiring a trawl back through conversation history. -
ts-retrainsupports two ways of reaching that confirmation: the default is a human explicitly approving the redeploy in conversation. An optional autonomous mode exists for when a human or a standing project instruction has explicitly pre-authorized unattended retraining for a specific series -- in that mode, the skill callsexecute_redeploy(confirmed=True, autonomous=True)itself the momentshould_redeploy: truecomes back, with no pause. Autonomous mode is never assumed; the skill's own instructions tell it to fall back to human-confirmed mode whenever authorization is ambiguous. This is no longer purely a prose contract --autonomous=Truetriggers a real code-level check (check_autonomous_mode) against a standing authorization record, andexecute_redeployrefuses to act, even withconfirmed=True, if no such record exists for the series. See the next bullet. -
ts-retrain__authorize_autonomous_mode(withrevoke_autonomous_mode/check_autonomous_mode) makes autonomous-mode authorization inspectable, not just conversational -- closing a gap this project's own introductory post flagged as unfinished business. Previously, "is autonomous mode on for this series" lived entirely in the agent's memory of a conversation; now it's a small persisted JSON record (autonomous_mode.jsonby default, next to the series CSV -- a separate file from the deployment manifest, since authorization and deployment are different concerns with different lifecycles) holdingauthorized,authorized_at,authorized_by, and an optionalnote.authorize_autonomous_modeperforms no judgment of its own about whether granting it is appropriate -- it only persists a decision that's already been made by a human or a standing project instruction, the same wayrecord_deploymentpersists a deployment decision rather than making one. -
ts-retrainnow has TWO pieces of durable state, not one -- the deployment manifest (deployment_manifest.json) and the new autonomous-mode authorization record (autonomous_mode.json), both written next to the series CSV. Everything else in the whole toolkit remains a pure function of its explicit inputs. -
execute_redeployrequires thedeployextra installed (it delegates toomen.deploy.forecast_tools), regardless of whichmodel_typeis requested, since it imports that module as a whole.ts-retrain's diagnostic and record-keeping tools (load_deployment_manifest,compare_candidate_to_deployed,record_deployment,authorize_autonomous_mode,revoke_autonomous_mode,check_autonomous_mode) have no such requirement -- all plainjson/os/datetime, nothing beyond core.
Next steps
Layer 5's optional autonomous mode (see above) is fully built: ts-retrain
can either pause for human confirmation before redeploying, or -- given
explicit, unambiguous authorization for a specific series -- call
execute_redeploy(confirmed=True, autonomous=True) itself the moment
should_redeploy: true comes back. Both the confirmation gate
(execute_redeploy refusing to act without confirmed=True) AND the
autonomous-mode authorization check (execute_redeploy refusing
autonomous=True without a standing check_autonomous_mode record) are
now mechanical and tested, not just prose contracts the skill is trusted
to follow correctly. This closes the gap flagged as unfinished business
in this project's introductory post -- "is autonomous mode on for this
series" no longer depends solely on what the agent remembers being told;
authorize_autonomous_mode/revoke_autonomous_mode/check_autonomous_mode
make it a small, inspectable, persisted record instead.
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 omen_agentic_forecasting-0.0.1.tar.gz.
File metadata
- Download URL: omen_agentic_forecasting-0.0.1.tar.gz
- Upload date:
- Size: 4.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6fd4d07bafb6ccda37e85a2db3c99df36bd00c2233e813a17377ed0b441804f
|
|
| MD5 |
73f8870608bcb282936354e17bea8f49
|
|
| BLAKE2b-256 |
1d5efd3d474ef45362059c4a7071ea331285e08448da37f10450ca333478dba3
|
File details
Details for the file omen_agentic_forecasting-0.0.1-py3-none-any.whl.
File metadata
- Download URL: omen_agentic_forecasting-0.0.1-py3-none-any.whl
- Upload date:
- Size: 133.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d4e30a65e7b4257ff4b5a266aae0566a73ed669ab04995e5b5ec2b544fcc8ae
|
|
| MD5 |
a95cb71e7ffcc12b0847db1e5b917df6
|
|
| BLAKE2b-256 |
7a8df8320b46a5a9f9bca340009c430dd57910dc669356018ff5c9097bd95531
|