{"id":"3cdb1a3c-62fd-4bfb-bbe5-5ba96950fb59","entityType":"agent","slug":"clawhub-psyb0t-predictalot","name":"predictalot","canonicalUrl":"https://www.xpersona.co/agent/clawhub-psyb0t-predictalot","canonicalPath":"/agent/clawhub-psyb0t-predictalot","generatedAt":"2026-10-11T03:54:31.182Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T23:45:17.789Z","emptyReason":null},"description":"Self-hosted forecasting/prediction service. Foundation time-series endpoints under /v1/timeseries/<type>/{forecast,forecast/ensemble} + GET .../models — univariate, past/future/both covariates, multivariate, samples — over 5 zero-shot models (chronos-2, timesfm-2.5, moirai-2, toto-1, sundial-base-128m). Plus supervised tabular ML under /v1/tabular/ (9 backends — lightgbm, xgboost, hist-gbt, random-forest, logistic, mlp, svm-rbf, knn, naive-bayes — over direction/value/quantile modes) with train+persist, weighted ensembles, and calibrated/stacking/diversified meta-learners. Unified REST + MCP (streamable-HTTP at /mcp, one tool per (type, model) cell + per-type ensemble + listing) + optional bearer auth. Use when the user wants to forecast a numeric time series (quantile bands or raw sample paths), condition a forecast on known/future covariates, ensemble several forecasters, or train a tabular model on engineered features and predict direction/value/quantiles on the latest snapshot.","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.2K downloads reported by the source. Last updated 10/10/2026.","installCommand":"clawhub skill install s17fq93tmpky791n7516jcn08n83sfn2:predictalot","sourceUrl":"https://clawhub.ai/psyb0t/predictalot","homepage":"https://clawhub.ai/psyb0t/skills/predictalot","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/psyb0t/predictalot","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/psyb0t/skills/predictalot","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":62,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"predictalot technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-10T23:45:17.789Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T23:45:17.789Z","emptyReason":null},"stars":null,"forks":null,"downloads":1227,"packageName":null,"latestVersion":"1.2.2","tractionLabel":"1.2K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T23:45:17.774Z","emptyReason":null},"lastUpdatedAt":"2026-10-10T23:45:17.789Z","lastCrawledAt":"2026-10-10T23:45:17.774Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-11T23:45:17.774Z","lastVerifiedAt":null,"highlights":[{"version":"1.2.2","createdAt":"2026-10-10T15:00:46.805Z","changelog":"- Removed the file: skill-card.md - No changes to features or functionality. - Internal documentation/metadata update only; skill usage is unaffected.","fileCount":5,"zipByteSize":17867},{"version":"1.2.1","createdAt":"2026-09-24T20:24:09.686Z","changelog":"- Removed the file skill-card.md. - No user-facing features or behavioral changes. - Documentation or presentation only: maintenance/reduction in redundant content.","fileCount":5,"zipByteSize":17885},{"version":"1.2.0","createdAt":"2026-09-24T18:24:46.088Z","changelog":"predictalot 1.2.0 - Added explicit model lifecycle controls: new POST /v1/models/unload endpoint to release all resident foundation models; forecast requests can now include unload: true to release models after use; MCP equivalent command unload_models supported. - Documentation updates: SKILL.md expanded with model lifecycle/unloading instructions and references to the new endpoints. - Removed legacy or redundant documentation (skill-card.md).","fileCount":5,"zipByteSize":17961},{"version":"1.1.9","createdAt":"2026-08-01T20:35:16.841Z","changelog":"- Removed the file skill-card.md. - No functional or API changes; all features, endpoints, and documentation remain unchanged. - Documentation is now consolidated in SKILL.md only.","fileCount":5,"zipByteSize":17765},{"version":"1.1.8","createdAt":"2026-07-27T23:57:05.450Z","changelog":"- Removed the skill-card.md file. - No user-visible changes to functionality or endpoints. - Documentation and setup unchanged; core service features remain as before.","fileCount":5,"zipByteSize":17784},{"version":"1.1.7","createdAt":"2026-07-27T23:28:10.966Z","changelog":"- Removed the skill-card.md file. - No other changes to functionality or documentation. - Housekeeping/minor cleanup only; no user-facing impacts.","fileCount":5,"zipByteSize":17696},{"version":"1.1.6","createdAt":"2026-07-27T15:32:22.882Z","changelog":"- Removed the skill-card.md file. - No user-facing features changed; this update only affects repository documentation files.","fileCount":5,"zipByteSize":17097},{"version":"1.1.5","createdAt":"2026-07-27T13:46:20.638Z","changelog":"- Removed the skill-card.md file. - No changes to core functionality or endpoints. - No user-facing feature or API modifications.","fileCount":5,"zipByteSize":17754}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17fq93tmpky791n7516jcn08n83sfn2:predictalot","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s17fq93tmpky791n7516jcn08n83sfn2:predictalot` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/psyb0t/predictalot before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-predictalot/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-predictalot/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-predictalot/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-predictalot/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-predictalot/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-predictalot/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T03:54:31.175Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-predictalot/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-predictalot/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-predictalot/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-psyb0t-predictalot/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T23:45:17.789Z","emptyReason":null},"readme":"Skill: predictalot\n\nOwner: psyb0t\n\nSummary: Self-hosted forecasting/prediction service. Foundation time-series endpoints under /v1/timeseries/<type>/{forecast,forecast/ensemble} + GET .../models — univariate, past/future/both covariates, multivariate, samples — over 5 zero-shot models (chronos-2, timesfm-2.5, moirai-2, toto-1, sundial-base-128m). Plus supervised tabular ML under /v1/tabular/ (9 backends — lightgbm, xgboost, hist-gbt, random-forest, logistic, mlp, svm-rbf, knn, naive-bayes — over direction/value/quantile modes) with train+persist, weighted ensembles, and calibrated/stacking/diversified meta-learners. Unified REST + MCP (streamable-HTTP at /mcp, one tool per (type, model) cell + per-type ensemble + listing) + optional bearer auth. Use when the user wants to forecast a numeric time series (quantile bands or raw sample paths), condition a forecast on known/future covariates, ensemble several forecasters, or train a tabular model on engineered features and predict direction/value/quantiles on the latest snapshot.\n\nTags: latest:1.2.2\n\nVersion history:\n\nv1.2.2 | 2026-10-10T15:00:46.805Z | auto\n\n- Removed the file: skill-card.md\n- No changes to features or functionality.\n- Internal documentation/metadata update only; skill usage is unaffected.\n\nv1.2.1 | 2026-09-24T20:24:09.686Z | auto\n\n- Removed the file skill-card.md.\n- No user-facing features or behavioral changes.\n- Documentation or presentation only: maintenance/reduction in redundant content.\n\nv1.2.0 | 2026-09-24T18:24:46.088Z | auto\n\npredictalot 1.2.0\n\n- Added explicit model lifecycle controls: new POST /v1/models/unload endpoint to release all resident foundation models; forecast requests can now include unload: true to release models after use; MCP equivalent command unload_models supported.\n- Documentation updates: SKILL.md expanded with model lifecycle/unloading instructions and references to the new endpoints.\n- Removed legacy or redundant documentation (skill-card.md).\n\nv1.1.9 | 2026-08-01T20:35:16.841Z | auto\n\n- Removed the file skill-card.md.\n- No functional or API changes; all features, endpoints, and documentation remain unchanged.\n- Documentation is now consolidated in SKILL.md only.\n\nv1.1.8 | 2026-07-27T23:57:05.450Z | auto\n\n- Removed the skill-card.md file.\n- No user-visible changes to functionality or endpoints.\n- Documentation and setup unchanged; core service features remain as before.\n\nv1.1.7 | 2026-07-27T23:28:10.966Z | auto\n\n- Removed the skill-card.md file.\n- No other changes to functionality or documentation.\n- Housekeeping/minor cleanup only; no user-facing impacts.\n\nv1.1.6 | 2026-07-27T15:32:22.882Z | auto\n\n- Removed the skill-card.md file.\n- No user-facing features changed; this update only affects repository documentation files.\n\nv1.1.5 | 2026-07-27T13:46:20.638Z | auto\n\n- Removed the skill-card.md file.\n- No changes to core functionality or endpoints.\n- No user-facing feature or API modifications.\n\nv1.1.4 | 2026-07-26T12:22:44.158Z | auto\n\npredictalot 1.1.4\n\n- Removed the skill-card.md file (no longer included in the distribution).\n- No user-facing feature or API changes.\n\nv1.1.3 | 2026-07-26T09:48:50.502Z | auto\n\n- Removed the file: skill-card.md\n- No user-facing functionality or API changes\n- Documentation and core skill behavior remain unchanged\n\nv1.1.2 | 2026-07-26T01:54:03.072Z | auto\n\n**predictalot 1.1.2**\n\n- Added a security notice: all data sent via predictalot (time series, feature values, model IDs) leaves your host and is transmitted to the configured service; users should only connect to instances they run or explicitly trust.\n- Minor documentation changes in SKILL.md to reflect the new data transmission warning.\n- Removed the skill-card.md file.\n\nv1.1.1 | 2026-07-25T23:43:44.152Z | auto\n\npredictalot 1.1.1\n\n- Added a permissions section describing required network and shell access in SKILL.md.\n- Removed redundant skill-card.md file.\n- No changes to functionality or APIs. Documentation only.\n\nv1.1.0 | 2026-07-25T22:20:25.842Z | auto\n\npredictalot 1.1.0\n\n- Added a new \"Security & safety\" section to documentation, highlighting authentication, safe network exposure, and destructive delete safeguards.\n- Clarified that the skill is \"consumer-only\" and never starts, provisions, or hardens the backend server.\n- Added warnings about exposure of the HTTP port and best practices for operator security (binding, token management).\n- Made explicit that destructive model deletions require user confirmation and must use recently-obtained model IDs.\n- Removed the redundant skill-card.md file.\n\nv1.0.3 | 2026-07-25T19:07:40.360Z | auto\n\n- Expanded and clarified documentation in SKILL.md, detailing all available time-series and tabular forecasting/model endpoints, supported models, types, and usage scenarios.\n- Added a comprehensive quick start guide with example curl requests for health checks and predictions.\n- Specified authentication options, including bearer token or optional open access.\n- Provided tables for supported time-series model types and tabular ML backends, including licensing notes and recommended use cases.\n- Outlined clear guidance on when the skill should and should not be used, along with explicit setup instructions.\n\nArchive index:\n\nArchive v1.2.2: 5 files, 17867 bytes\n\nFiles: references/setup.md (7810b), scripts/predictalot.sh (2122b), skill-card.md (2188b), SKILL.md (34632b), _meta.json (130b)\n\nFile v1.2.2:SKILL.md\n\n---\nname: predictalot\ndescription: Self-hosted forecasting/prediction service. Foundation time-series endpoints under /v1/timeseries/<type>/{forecast,forecast/ensemble} + GET .../models — univariate, past/future/both covariates, multivariate, samples — over 5 zero-shot models (chronos-2, timesfm-2.5, moirai-2, toto-1, sundial-base-128m). Plus supervised tabular ML under /v1/tabular/ (9 backends — lightgbm, xgboost, hist-gbt, random-forest, logistic, mlp, svm-rbf, knn, naive-bayes — over direction/value/quantile modes) with train+persist, weighted ensembles, and calibrated/stacking/diversified meta-learners. Unified REST + MCP (streamable-HTTP at /mcp, one tool per (type, model) cell + per-type ensemble + listing) + optional bearer auth. Use when the user wants to forecast a numeric time series (quantile bands or raw sample paths), condition a forecast on known/future covariates, ensemble several forecasters, or train a tabular model on engineered features and predict direction/value/quantiles on the latest snapshot.\nhomepage: https://github.com/psyb0t/docker-predictalot\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🔮\", \"primaryEnv\": \"PREDICTALOT_URL\", \"requires\": { \"bins\": [\"docker\", \"curl\"] } } }\npermissions:\n  network: \"outbound HTTP(S) to the configured PREDICTALOT_URL only (forecast/train/model-management calls + MCP at /mcp)\"\n  shell: \"docker + curl invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# predictalot\n\nSelf-hosted forecasting service — one HTTP container, two model families.\n\n- **Foundation time-series (zero-shot)** — 5 forecasters (`chronos-2`, `timesfm-2.5`, `moirai-2`, `toto-1`, `sundial-base-128m`). Hand them a context window, get quantile bands or raw sample paths. No training step. Routed by forecast type under `/v1/timeseries/<type>/` — `univariate`, `covariates/past`, `covariates/future`, `covariates` (past+future), `multivariate`, `samples`. Each type has `forecast`, `forecast/ensemble`, and a `models` listing.\n- **Tabular ML (supervised)** — 9 learners (`lightgbm`, `xgboost`, `hist-gbt`, `random-forest`, `logistic`, `mlp`, `svm-rbf`, `knn`, `naive-bayes`), all supporting 3 modes (`direction` / `value` / `quantile`). Train on YOUR engineered features, persist server-side by `modelId`, forecast on the latest feature snapshot. Weighted ensembles over stored models plus 3 meta-learners (`calibrated` / `stacking` / `diversified`). Under `/v1/tabular/`.\n- **MCP** — streamable-HTTP tools at `/mcp`. One tool per (FM type, model) cell plus per-type ensemble + listing. Tabular is HTTP-only.\n- **Model lifecycle** — `POST /v1/models/unload` releases every resident foundation model. A forecast's `unload: true` waits for concurrent forecasts using that model, then tears it down. The MCP equivalent is `unload_models`.\n- **Auth** — optional bearer token (`PREDICTALOT_AUTH_TOKENS` on the server; refuses to start with no tokens unless `PREDICTALOT_ALLOW_NO_AUTH=1`).\n\nFor installation, configuration, and container setup, see [references/setup.md](references/setup.md).\n\n## Security & safety\n\n- **Network-exposed** — predictalot is a plain HTTP + MCP service; anyone who can reach the port can call it. Set `PREDICTALOT_AUTH_TOKENS` to a strong generated secret (`$(openssl rand -hex 32)`) and bind to loopback (`-p 127.0.0.1:8080:8080`) by default. Only expose beyond loopback behind a reverse proxy / VPN, and never with the default/example token.\n- **External transmission** — every forecast/train call sends your time series, engineered feature values, and/or `modelId`s to whatever `PREDICTALOT_URL` points at — that data leaves your host. Point it only at a service you run or explicitly trust; prefer HTTPS.\n- **Consumer-only** — this skill talks to an instance you already run and trusts. It never provisions, starts, or hardens the server; that's the operator's job (see setup.md).\n- **Destructive delete requires confirmation** — `DELETE /v1/tabular/models/{modelId}` permanently removes a trained model and cannot be undone. Only call it against a `modelId` you obtained from a prior `GET /v1/tabular/models` (or a train response) in this session, and get explicit user confirmation before issuing the delete.\n\n## When To Use\n\n- Forecast a numeric time series N steps ahead and get calibrated quantile bands (`0.1`/`0.5`/`0.9`, etc.) — zero-shot, no training.\n- Get raw Monte-Carlo sample paths (via the `samples` type) to compute custom risk metrics / joint distributions across horizon steps.\n- Condition a forecast on covariates: known-history (`covariates/past`), forward-known drivers like a planned promotion or price schedule (`covariates/future`), or both at once (`covariates`).\n- Forecast several correlated channels jointly (`multivariate`).\n- Combine multiple forecasters into a weighted ensemble and inspect each member's individual forecast + applied weight.\n- Train a supervised model on engineered features and predict next-bar direction (`P(up)`), a point value, or quantiles on the latest snapshot.\n- Combine trained tabular models via ensemble or a `calibrated` / `stacking` / `diversified` meta-learner.\n\n## When NOT To Use\n\n- Real-time / streaming forecasts — every endpoint is request/response only.\n- Automatic feature engineering on the tabular side — you supply the features; predictalot does not derive indicators or lags for you.\n- `timesfm-2.5` for accuracy — it is the weakest of the five on every benchmarked dataset. Skip it or set `weights={\"timesfm-2.5\": 0}` in ensembles.\n- Commercial use of `moirai-2` — it ships under CC-BY-NC-4.0 (non-commercial). The other four FMs are Apache 2.0.\n- Covariate/multivariate/samples types on models that don't support them — membership is fixed per type (see below). A non-member `model` → 400.\n- Provisioning or hardening the server from here — this skill is a **consumer**. It talks to an instance the user already runs and trusts; it never launches, escalates, or reconfigures the container.\n\n## Setup\n\nThe container should already be running. Point at it:\n\n```bash\nexport PREDICTALOT_URL=http://localhost:8080\n```\n\nIf the server has `PREDICTALOT_AUTH_TOKENS` set, export a token too:\n\n```bash\nexport PREDICTALOT_AUTH_TOKEN=<your-token>\n# every /v1/* and /mcp request below needs: -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\"\n```\n\n**Verify:** `curl $PREDICTALOT_URL/healthz` returns `{\"ok\": true}`. (`/healthz` is unauthenticated.)\n\nFor install / configuration / env vars / CPU vs CUDA images, see [references/setup.md](references/setup.md).\n\n## Models & Types\n\nFoundation models, and which forecast types each supports (a `model` outside a type's member set → 400):\n\n| Model | Univariate | Multivariate | Cov: past | Cov: future | Cov: both | Samples | License | Recommended for |\n|---|:-:|:-:|:-:|:-:|:-:|:-:|---|---|\n| `chronos-2` | ✓ | ✓ | ✓ | ✓ | ✓ | — | Apache 2.0 | Default all-rounder; fastest on CPU; only model with future/both covariates. |\n| `timesfm-2.5` | ✓ | — | — | — | — | — | Apache 2.0 | Weakest on benchmarks — skip or zero-weight it. Univariate-only; compile-time horizon cap. |\n| `moirai-2` | ✓ | ✓ | ✓ | — | — | — | CC-BY-NC-4.0 | Clean cyclic/seasonal series; correlated channels. Non-commercial license. |\n| `toto-1` | ✓ | ✓ | — | — | — | ✓ | Apache 2.0 | Noisy / observability / financial series; exposes raw sample paths. |\n| `sundial-base-128m` | ✓ | — | — | — | — | ✓ | Apache 2.0 | Drifting / trending series; generative sample paths. Runs in a sidecar venv. |\n\nType → URL prefix:\n\n| Type | URL prefix | Members |\n|---|---|---|\n| univariate | `/v1/timeseries/univariate` | chronos-2, timesfm-2.5, moirai-2, toto-1, sundial-base-128m |\n| multivariate | `/v1/timeseries/multivariate` | chronos-2, moirai-2, toto-1 |\n| covariates (past) | `/v1/timeseries/covariates/past` | chronos-2, moirai-2 |\n| covariates (future) | `/v1/timeseries/covariates/future` | chronos-2 |\n| covariates (past+future) | `/v1/timeseries/covariates` | chronos-2 |\n| samples | `/v1/timeseries/samples` | toto-1, sundial-base-128m |\n\nDiscover live per-type membership + runtime state with `GET /v1/timeseries/<type>/models`. Discover tabular backends with `GET /v1/tabular/backends`.\n\nTabular backends (all support `direction` / `value` / `quantile`):\n\n| Slug | Display name | Category |\n|---|---|---|\n| `lightgbm` | LightGBM | boosting |\n| `xgboost` | XGBoost | boosting |\n| `hist-gbt` | HistGradientBoosting (sklearn) | boosting |\n| `random-forest` | Random Forest | bagging |\n| `logistic` | Logistic / Ridge / QuantileRegressor (linear baselines) | linear |\n| `mlp` | Multi-Layer Perceptron (sklearn) | neural |\n| `svm-rbf` | SVM with RBF kernel | kernel |\n| `knn` | k-Nearest Neighbors | distance |\n| `naive-bayes` | Gaussian Naive Bayes / BayesianRidge | independence |\n\n## Quick Start\n\n> Every forecast/train call below sends your time series, engineered features, and/or `modelId`s over the network to `$PREDICTALOT_URL`. Only point this at a trusted, self-hosted instance you control, prefer HTTPS, treat proprietary datasets as sensitive, and never echo `PREDICTALOT_AUTH_TOKEN` in output.\n\n```bash\n# Health (unauthenticated).\ncurl -s $PREDICTALOT_URL/healthz | jq\n\n# List univariate models + their runtime state.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n\n# Zero-shot univariate forecast: 5 steps ahead of one series, chronos-2.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n        \"model\": \"chronos-2\",\n        \"context\": [[10,11,12,13,14,15,16,17,18,19,20]],\n        \"config\": { \"horizon\": 5, \"quantileLevels\": [0.1, 0.5, 0.9] }\n      }' | jq\n```\n\nWire format is **camelCase** (`quantileLevels`, `contextLength`, `pastCovariates`, `futureCovariates`, `numSamples`, `memberOverrides`, `modelId`). All `/v1/*` routes require the bearer header when the server has tokens configured; drop the header for an open-auth deployment.\n\nShapes recur across the timeseries API: `context` is `[series][time]` (a batch of independent series), `horizon` > 0, `quantileLevels` is a subset of `{0.1, 0.2, …, 0.9}` (default `[0.1, 0.5, 0.9]`), `contextLength` caps history fed to the model (omit → per-model default), `unload: true` tears the model down after the response. `median` and `quantiles[\"0.5\"]` are the same for most models but can differ for chronos-2 (its `median` is the distribution mean).\n\nRelease every resident foundation model when the next task will not need one:\n\n```bash\ncurl -s -X POST $PREDICTALOT_URL/v1/models/unload \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n```\n\nThe request returns `409` while a foundation forecast is active. Do not retry it blindly. Wait for the forecast result, then retry when memory needs to be freed.\n\n---\n\n## API — `POST /v1/timeseries/univariate/forecast`\n\nSingle-model quantile forecast over a batch of independent series. `context` (your time series data) transmits off-box to `$PREDICTALOT_URL` — same data-transfer note as Quick Start applies to every timeseries endpoint below.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `model` | yes | — | One of the univariate members. Not a member → 400; unknown slug → 404. |\n| `context` | yes | — | `[series][time]` — one inner list of floats per series. |\n| `config.horizon` | yes | — | Steps ahead. Must be `> 0` (else 422). |\n| `config.quantileLevels` | no | `[0.1, 0.5, 0.9]` | Subset of `{0.1..0.9}` step 0.1. Out-of-range → 400. |\n| `config.contextLength` | no | per-model | Max history points fed to the model. `> 0`. |\n| `config.extra` | no | — | Per-backend escape-hatch dict, camelCased. Unknown keys ignored. E.g. chronos-2: `batchSize`, `crossLearning`, `limitPredictionLength`; toto-1: `numSamples`, `samplesPerBatch`, `useKvCache`; timesfm-2.5: `normalizeInputs`, `fixQuantileCrossing`, … |\n| `unload` | no | `false` | Unload the model after responding. |\n\n### Response\n\n```json\n{\n  \"model\": \"chronos-2\",\n  \"horizon\": 5,\n  \"quantileLevels\": [0.1, 0.5, 0.9],\n  \"median\": [[20.9, 21.8, 22.7, 23.6, 24.5]],\n  \"quantiles\": {\n    \"0.1\": [[20.1, 20.8, 21.5, 22.1, 22.7]],\n    \"0.5\": [[20.9, 21.8, 22.7, 23.6, 24.5]],\n    \"0.9\": [[21.7, 22.9, 24.0, 25.1, 26.3]]\n  }\n}\n```\n\n`median` is `[series][time]`; `quantiles` maps each level string → `[series][time]`.\n\n### Error Contract\n\n| Status | Shape | When |\n|---|---|---|\n| 200 | forecast JSON | success |\n| 400 | `{\"detail\": \"...\"}` | empty context/series, model not a member of the type, quantile level outside `{0.1..0.9}`, horizon over a model's compile-time cap (timesfm-2.5 / moirai-2) |\n| 401 | `{\"detail\": \"...\"}` | tokens configured, missing/wrong bearer |\n| 404 | `{\"detail\": \"...\"}` | unknown `model` slug |\n| 413 | `{\"detail\": \"...\"}` | body over `PREDICTALOT_MAX_BODY_SIZE` |\n| 422 | `{\"detail\": [...]}` | Pydantic validation (missing/typed fields, `horizon` not `> 0`) |\n| 503 | `{\"detail\": \"...\"}` | snapshot download failed, inference threw, or sundial sidecar unreachable |\n\n---\n\n## API — `POST /v1/timeseries/univariate/forecast/ensemble`\n\nWeighted mean across every univariate member. No `model` field — membership is the whole type.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `context` | yes | — | `[series][time]`. |\n| `config` | yes | — | Same `ForecastConfig` as single forecast (`horizon`, `quantileLevels`, `contextLength`, `extra`). |\n| `weights` | no | uniform | `{slug: float ≥ 0}`. Missing slugs default to 1.0; weight `0` disables a member; unknown slug → 400. |\n| `memberOverrides` | no | — | `{slug: partial-config}` — per-member overrides of the global `config` (e.g. give one member a different `contextLength` or `extra`). |\n| `unload` | no | `false` | Unload members after responding. |\n\n### Response\n\nAggregated `median` + `quantiles` (same shapes as single forecast) with `model: \"ensemble\"`, plus:\n\n```json\n{\n  \"model\": \"ensemble\",\n  \"horizon\": 5,\n  \"quantileLevels\": [0.1, 0.5, 0.9],\n  \"median\": [[...]],\n  \"quantiles\": { \"0.1\": [[...]], \"0.5\": [[...]], \"0.9\": [[...]] },\n  \"ensembleMembers\": [\"chronos-2\", \"moirai-2\", \"toto-1\"],\n  \"weights\": { \"chronos-2\": 0.5, \"moirai-2\": 0.25, \"toto-1\": 0.25 },\n  \"individual\": {\n    \"chronos-2\": { \"model\": \"chronos-2\", \"horizon\": 5, \"quantileLevels\": [...],\n                   \"median\": [[...]], \"quantiles\": {...}, \"weight\": 0.5 }\n  }\n}\n```\n\n`individual[slug]` is that member's full single-forecast response + its applied `weight`.\n\n### Error Contract\n\nSame as single univariate, minus 404 (no `model` field) — an unknown `weights` slug is a 400, not 404.\n\n---\n\n## API — `POST /v1/timeseries/covariates/past/forecast`\n\nForecast each target series conditioned on covariates known only up to `t`. Members: `chronos-2`, `moirai-2`.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `model` | yes | — | A past-covariate member. |\n| `context` | yes | — | `[series][time]` — the univariate targets. |\n| `pastCovariates` | yes | — | `list[dict[name, float[]]]` — one mapping per series; every value array is the **same length as that series' context**. All series share the same covariate names. |\n| `config` | yes | — | `ForecastConfig` (`horizon`, `quantileLevels`, `contextLength`, `extra`). |\n| `unload` | no | `false` | |\n\n### Response\n\nIdentical shape to univariate single forecast (`model`, `horizon`, `quantileLevels`, `median`, `quantiles`).\n\n### Error Contract\n\nSame table as univariate, plus 400 when a `pastCovariates` value length doesn't match the matching `context` series.\n\n---\n\n## API — `POST /v1/timeseries/covariates/past/forecast/ensemble`\n\nWeighted ensemble over the past-covariate members. Fields = past forecast fields plus `weights` + `memberOverrides` (as in the univariate ensemble). Response = univariate ensemble shape.\n\n---\n\n## API — `POST /v1/timeseries/covariates/future/forecast`\n\nForecast conditioned on covariates known only over the **future window** (length == `horizon`) — e.g. a planned promotion, a published price schedule, a weather forecast. Member: `chronos-2` only.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `model` | yes | — | `chronos-2`. |\n| `context` | yes | — | `[series][time]` targets. |\n| `futureCovariates` | yes | — | `list[dict[name, float[]]]` — one mapping per series; **each value array of length `horizon`**. All series share names. |\n| `config` | yes | — | `ForecastConfig`. |\n| `unload` | no | `false` | |\n\n### Response\n\nUnivariate single-forecast shape.\n\n### Error Contract\n\nUnivariate table + 400 when a `futureCovariates` value length ≠ `config.horizon`.\n\n---\n\n## API — `POST /v1/timeseries/covariates/future/forecast/ensemble`\n\nEnsemble over future-covariate members (currently just `chronos-2`, so only useful once more back it). Fields = future forecast + `weights` + `memberOverrides`. Response = univariate ensemble shape.\n\n---\n\n## API — `POST /v1/timeseries/covariates/forecast` (past + future)\n\nCombined past **and** future covariates in one call. Member: `chronos-2` only.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `model` | yes | — | `chronos-2`. |\n| `context` | yes | — | `[series][time]` targets. |\n| `pastCovariates` | yes | — | Per series, same length as context. |\n| `futureCovariates` | yes | — | Per series, length `horizon`. **Every future-covariate name MUST also appear in `pastCovariates`** for that series (chronos-2 constraint) — else 400. |\n| `config` | yes | — | `ForecastConfig`. |\n| `unload` | no | `false` | |\n\n### Response\n\nUnivariate single-forecast shape.\n\n### Error Contract\n\nUnivariate table + 400 for length mismatches or a future-covariate name missing from `pastCovariates`.\n\n---\n\n## API — `POST /v1/timeseries/covariates/forecast/ensemble`\n\nEnsemble over past+future members. Fields = past+future forecast + `weights` + `memberOverrides`. Response = univariate ensemble shape.\n\n---\n\n## API — `GET /v1/timeseries/<type>/models`\n\nPer-type model listing + runtime state. `<type>` ∈ `univariate`, `multivariate`, `covariates/past`, `covariates/future`, `covariates`, `samples`.\n\n### Response\n\n```json\n{\n  \"type\": \"univariate\",\n  \"models\": [\n    { \"slug\": \"chronos-2\", \"loaded\": true, \"lastUsedSecsAgo\": 12.4, \"idleTimeoutSecs\": 1800.0 },\n    { \"slug\": \"timesfm-2.5\", \"loaded\": false, \"lastUsedSecsAgo\": null, \"idleTimeoutSecs\": 1800.0 }\n  ]\n}\n```\n\n`lastUsedSecsAgo` is `null` when the model has never loaded. `idleTimeoutSecs` reflects `PREDICTALOT_MODEL_IDLE_TIMEOUT` (or a per-slug override); `0` means never auto-unloaded.\n\n### Error Contract\n\n200 on success; 401 when tokens are configured and the bearer is missing/wrong.\n\n---\n\n## API — `GET /v1/tabular/backends`\n\nList the supervised tabular backends and the modes each supports. Use this to discover the live set before a train call.\n\n### Response\n\n```json\n{\n  \"backends\": [\n    { \"slug\": \"lightgbm\", \"displayName\": \"LightGBM\", \"category\": \"boosting\",\n      \"supportedModes\": [\"direction\", \"quantile\", \"value\"] },\n    { \"slug\": \"xgboost\", \"displayName\": \"XGBoost\", \"category\": \"boosting\",\n      \"supportedModes\": [\"direction\", \"quantile\", \"value\"] }\n  ]\n}\n```\n\n### Error Contract\n\n200 on success; 401 when tokens are configured and the bearer is missing/wrong.\n\n---\n\n## Tabular API (train → forecast)\n\nThe tabular side trains on YOUR engineered features and persists a model server-side by `modelId`. `target` / `features` are generic float lists — no OHLC / indicator assumptions. `features` is per series: `featureName → time-aligned float list`. Same data-transfer note as Quick Start applies: `target`/`features`/`modelId` all transmit to `$PREDICTALOT_URL`.\n\n### `POST /v1/tabular/train`\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `modelId` | yes | — | Caller-chosen id, used for storage + later forecast lookup. |\n| `backend` | yes | — | A slug from `GET /v1/tabular/backends`. Unknown → 404. |\n| `target` | yes | — | `[series][time]` — the scalar series to predict. Empty → 400. |\n| `features` | yes | — | `list[dict[name, float[]]]` — same length + names per series as `target`. Count must match `target` (else 400); differing key sets across series → 400. |\n| `config.mode` | yes | — | `direction` (sign of `target[t+h] - target[t]`) / `value` (regress `target[t+h]`) / `quantile`. Mode not supported by the backend → 400. |\n| `config.horizon` | yes | — | Bars ahead (`t+h`). `> 0`. |\n| `config.quantileLevels` | when `quantile` | — | Required for `mode=\"quantile\"`; subset of `{0.1..0.9}`. |\n| `config.*` (tier-2) | no | — | `nEstimators`, `maxDepth`, `learningRate`, `numLeaves`, `minSamples`, `randomState`, `categoricalFeatures`, `monotonicConstraints`, `classWeight`, `sampleWeight`, `earlyStoppingRounds`, `validationFraction`. Backends ignore knobs they don't use. |\n| `config.extra` | no | — | Per-backend hyperparams (e.g. svm-rbf reads `C`/`gamma`; mlp reads `hiddenLayerSizes`/`activation`; knn reads `nNeighbors`/`weights`/`metric`). |\n| `overwrite` | no | `false` | Reuse an existing `modelId` → 409 unless `true`. |\n\n**Response** (`TrainResponse`): `modelId`, `backend`, `mode`, `horizon`, `nTrainingRows`, `nFeatures`, `featureNames`, `featureImportance` (`{name: float}`), `trainSecs`.\n\n### `POST /v1/tabular/forecast`\n\nRuns a stored model on the **latest** feature snapshot — the LAST row per series is the anchor; earlier rows are ignored.\n\n| Field | Required | Notes |\n|---|---|---|\n| `modelId` | yes | Missing → 404. If its backend is no longer registered → 410. |\n| `features` | yes | `list[dict[name, float[]]]`; names must match training. Missing a trained name → 400. |\n\n**Response** (`ForecastResponse`) is mode-dependent:\n- `direction`: `probUp: float[]` (per series) + `confidence: float[]` (`|probUp-0.5|*2`).\n- `value`: `predicted: float[]`.\n- `quantile`: `median: float[series][1]` + `quantiles: {level: float[series][1]}`.\n\n### `POST /v1/tabular/forecast/ensemble`\n\nWeighted combination of several stored models on the same features; all members must share `mode`, `horizon`, and `featureNames` (mismatch → 400).\n\n| Field | Required | Notes |\n|---|---|---|\n| `modelIds` | yes | Stored ids to combine (≥ 1). Any missing → 404. |\n| `weights` | no | `{modelId: float ≥ 0}`. None → uniform; `0` removes a member; unknown id or negative → 400. |\n| `features` | yes | As in forecast; last row per series is the anchor. |\n\n**Response** (`EnsembleForecastResponse`): `mode`, `horizon`, `ensembleMembers`, normalized `weights`, `individual` (`{modelId: member response}`), plus the combined mode-specific fields (`probUp`+`confidence` / `predicted` / `median`+`quantiles`).\n\n### `GET /v1/tabular/models` and `DELETE /v1/tabular/models/{modelId}` (destructive)\n\n`GET` lists stored models: `{\"models\": [{ modelId, backend, mode, horizon, nFeatures, featureNames, nTrainingRows, trainedAtUnix }]}`. `DELETE` removes one (`{\"modelId\": \"...\", \"removed\": true}`; 404 if absent) — **irreversible**. Only delete a `modelId` returned by a prior `GET /v1/tabular/models` call (or a train response), and get explicit user confirmation first.\n\n### Meta-learners\n\nComposite endpoints that hold out / cross-validate internally so you don't have to orchestrate it client-side. Each persists one blob under a `meta:<kind>` backend tag and forecasts via `POST /v1/tabular/forecast/<kind>` with `{modelId, features}`.\n\n- `POST /v1/tabular/train/calibrated` — base learner + post-hoc probability calibrator. **`direction` only.** Fields: `modelId`, `baseBackend`, `target`, `features`, `config` (mode must be `direction`), `calibrationMethod` (`sigmoid`|`isotonic`, default `sigmoid`), `calibrationFraction` (default `0.2`, in `(0,1)`), `overwrite`. Too-small split → 400.\n- `POST /v1/tabular/train/stacking` — K base learners + a meta-learner on K-fold OOF predictions. **`direction` only (v1).** Fields: `modelId`, `members` (≥ 2 `{backend, config}`, all `direction`, horizon == top-level), `metaBackend` (default `logistic`), `target`, `features`, `horizon`, `nFolds` (2–10, default 5), `overwrite`. Needs `≥ nFolds*5` rows. Response includes `oofScore` (AUC).\n- `POST /v1/tabular/train/diversified` — trains K candidates, selects a low-correlation subset by OOF Pearson correlation, combines equal-weight. Supports all 3 modes. Fields: `modelId`, `candidates` (≥ 2, mode+horizon must match top-level), `target`, `features`, `horizon`, `mode`, `quantileLevels` (required if `quantile`), `nFolds` (2–10, default 3), `maxPairwiseCorr` (0–1, default 0.85), `minMembers`, `maxMembers`, `overwrite`. Response includes `candidateCorr`.\n\nMeta-train responses (`MetaTrainResponse`): `modelId`, `kind`, `mode`, `horizon`, `membersUsed`, `nTrainingRows`, `nFeatures`, `featureNames`, `trainSecs`, plus `oofScore` (stacking) / `candidateCorr` (diversified). Meta-forecast responses (`MetaForecastResponse`): `modelId`, `kind`, `mode`, `horizon`, `members` (per-member breakdown), `selectedMembers` (diversified), and the mode-specific combined fields.\n\n### Tabular Error Contract\n\n| Status | When |\n|---|---|\n| 200 | success |\n| 400 | empty `target`; `target`/`features` count mismatch; differing feature key sets; mode unsupported by backend; `quantile` without `quantileLevels`; forecast features missing a trained name; ensemble member mode/horizon/features mismatch; unknown/negative ensemble weight; meta constraint violated (calibrated non-direction, stacking non-direction, diversified quantile w/o levels, split too small) |\n| 401 | tokens configured, missing/wrong bearer |\n| 404 | unknown tabular `backend` on train; `modelId` not found on forecast/meta |\n| 409 | train with `overwrite: false` against an existing `modelId` |\n| 410 | forecast against a `modelId` whose backend is no longer registered |\n| 413 | body over `PREDICTALOT_MAX_BODY_SIZE` |\n| 422 | Pydantic validation |\n| 503 | training/inference threw |\n\n---\n\n## MCP Endpoint (`/mcp`)\n\npredictalot mounts a [Model Context Protocol](https://modelcontextprotocol.io) server over Streamable HTTP at `/mcp`, in the same process, behind the same bearer auth. The tool surface mirrors the **foundation-model** routes only — tabular is HTTP-only.\n\nFor each forecast type there are three classes of tool:\n- `forecast_<type>_<model>` — single-model forecast for one (type, model) cell. Types are underscore-normalized: `univariate`, `multivariate`, `covariates_past`, `covariates_future`, `covariates_both`, `samples`; model slugs are underscore-normalized too (`chronos-2` → `chronos_2`). E.g. `forecast_univariate_chronos_2`, `forecast_covariates_future_chronos_2`.\n- `forecast_<type>_ensemble` — weighted ensemble over every model supporting that type.\n- `list_<type>_models` — which models implement the type + runtime state (`{type, models: [{slug, loaded, lastUsedSecsAgo, idleTimeoutSecs}]}`).\n\nTool args mirror the HTTP bodies but flattened (no nested `config`): `context`, `horizon`, `quantile_levels?`, `context_length?`, `unload?`; covariate tools add `past_covariates` / `future_covariates`; ensemble tools add `weights?`; samples tools use `num_samples?` instead of quantile levels and return `samples` `[series][sample][time]` + `median`.\n\nWire it into Claude Code (auth optional — the token may also be passed as `?apiToken=<token>`):\n\n```bash\nclaude mcp add --transport http predictalot $PREDICTALOT_URL/mcp \\\n  --header \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\"\n```\n\n### Raw JSON-RPC\n\nThe transport requires `Accept: application/json, text/event-stream`.\n\n```bash\n# tools/list — enumerate every (type, model) tool + ensembles + listings.\ncurl -s $PREDICTALOT_URL/mcp/ \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/list\"}'\n\n# tools/call — univariate chronos-2 forecast.\ncurl -s $PREDICTALOT_URL/mcp/ \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"tools/call\",\n    \"params\": {\n      \"name\": \"forecast_univariate_chronos_2\",\n      \"arguments\": {\n        \"context\": [[10,11,12,13,14,15,16,17,18,19,20]],\n        \"horizon\": 5,\n        \"quantile_levels\": [0.1, 0.5, 0.9]\n      }\n    }\n  }'\n```\n\nEach tool returns a JSON-encoded string. User-input errors come back as `{\"error\": \"...\", \"context\": \"<type>/<model>\"}`; internal errors are redacted to `{\"error\": \"internal error; see server logs\", \"context\": \"...\"}`.\n\n## Bearer-Token Auth\n\nIf `PREDICTALOT_AUTH_TOKENS` (comma-separated list) is set on the server, every `/v1/*` and `/mcp` request needs `Authorization: Bearer <one-of-the-tokens>`; `/healthz` is always open. Missing/wrong → 401. Tokens are compared constant-time (`hmac.compare_digest`).\n\n```bash\nexport PREDICTALOT_AUTH_TOKEN=<your-token>\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n```\n\nThe server **refuses to start** with an empty token list unless it was launched with `PREDICTALOT_ALLOW_NO_AUTH=1` — in that open-auth mode there is no 401 and no header is needed. For untrusted networks, combine the token with a reverse proxy doing TLS + rate limiting.\n\n## Typical Workflows\n\n### Pick a model → forecast → read the intervals\n\n```bash\n# 1. See which univariate models are available + resident.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq -r '.models[].slug'\n\n# 2. Forecast with chronos-2 (default all-rounder), 12 steps, three bands.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\":\"chronos-2\",\"context\":[[100,102,101,105,110,108,112,115,120,118,125,130]],\n       \"config\":{\"horizon\":12,\"quantileLevels\":[0.1,0.5,0.9]}}' | jq\n\n# 3. Read the interval: quantiles[\"0.5\"] is the central path,\n#    [\"0.1\"]/[\"0.9\"] the 80% band — all shaped [series][time].\n```\n\n### Ensemble, zero-weighting the weak model\n\n```bash\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/forecast/ensemble \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"context\":[[100,102,101,105,110,108,112,115,120,118,125,130]],\n       \"config\":{\"horizon\":12},\n       \"weights\":{\"timesfm-2.5\":0,\"chronos-2\":2,\"toto-1\":1}}' | jq \\\n  '{members: .ensembleMembers, weights, median}'\n```\n\n### Future-covariate forecast (chronos-2)\n\n```bash\n# Target has 8 steps of history; horizon 4 → futureCovariates arrays length 4.\ncurl -s $PREDICTALOT_URL/v1/timeseries/covariates/future/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\":\"chronos-2\",\n       \"context\":[[20,22,21,25,24,27,29,31]],\n       \"futureCovariates\":[{\"promo\":[0,1,1,0]}],\n       \"config\":{\"horizon\":4}}' | jq\n```\n\n### Raw sample paths for custom risk metrics\n\n```bash\ncurl -s $PREDICTALOT_URL/v1/timeseries/samples/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\":\"toto-1\",\n       \"context\":[[100,102,101,105,110,108,112,115,120,118,125,130]],\n       \"config\":{\"horizon\":10,\"numSamples\":256}}' | jq '{numSamples, shape: (.samples[0]|length)}'\n# samples is [series][sample][time]; compute your own VaR / quantiles off the draws.\n```\n\n### Train a tabular direction model, then forecast the latest snapshot\n\n```bash\n# 1. Train on engineered features (you supply them).\ncurl -s $PREDICTALOT_URL/v1/tabular/train \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"modelId\":\"trend-3\",\"backend\":\"lightgbm\",\n       \"target\":[[100,101,99,102,105,103,107,110,108,112]],\n       \"features\":[{\"rsi\":[55,58,52,60,63,59,65,70,66,72],\n                    \"mom\":[0.2,0.3,-0.1,0.4,0.5,0.2,0.6,0.7,0.4,0.8]}],\n       \"config\":{\"mode\":\"direction\",\"horizon\":3,\"nEstimators\":400}}' | jq\n\n# 2. Forecast — LAST feature row is the anchor.\ncurl -s $PREDICTALOT_URL/v1/tabular/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"modelId\":\"trend-3\",\"features\":[{\"rsi\":[72],\"mom\":[0.8]}]}' | jq\n# → {\"modelId\":\"trend-3\",\"backend\":\"lightgbm\",\"mode\":\"direction\",\"horizon\":3,\n#    \"probUp\":[0.63],\"confidence\":[0.26]}\n```\n\nFor a driver that runs a univariate forecast and pretty-prints the interval, see [`scripts/predictalot.sh`](scripts/predictalot.sh):\n\n```bash\nPREDICTALOT_URL=http://localhost:8080 \\\nPREDICTALOT_AUTH_TOKEN=<token> \\\n  bash scripts/predictalot.sh chronos-2 5 10 11 12 13 14 15 16 17 18 19 20\n```\n\n## Tips\n\n1. **Default to `chronos-2`** — fastest on CPU, widest type coverage (only model with future/both covariates), solid general-purpose accuracy.\n2. **Skip or zero-weight `timesfm-2.5`** — weakest on every benchmark. In ensembles pass `weights={\"timesfm-2.5\":0}`.\n3. **`moirai-2` is CC-BY-NC-4.0** — non-commercial. Fine for research/eval, not for a commercial product.\n4. **Use `samples` for risk work** — `toto-1` / `sundial-base-128m` return raw `[series][sample][time]` paths; compute your own VaR / joint metrics instead of trusting fixed quantile cuts.\n5. **`median` ≠ `quantiles[\"0.5\"]` for chronos-2** — its `median` is the distribution mean; the others agree.\n6. **Covariate length rules** — past covariates match the context length; future covariates match `horizon`; in the past+future type every future-cov name must also be a past-cov name.\n7. **Tabular features are yours** — the API does no feature engineering. The LAST row per series is the forecast anchor.\n8. **`overwrite: false` is the default** on every train endpoint — re-training a known `modelId` is a 409 until you pass `overwrite: true`.\n9. **Discover, don't assume** — `GET /v1/timeseries/<type>/models` and `GET /v1/tabular/backends` are the live source of truth for membership and modes.\n10. **First call is a cold load** — a model not yet resident pays a load (and download, if the snapshot isn't cached) on first request; subsequent calls are fast until the idle sweeper unloads it.\n\nFile v1.2.2:_meta.json\n\n{\n  \"ownerId\": \"kn79dhvmpjng4rp2jjk8k0v5xx80ccbk\",\n  \"slug\": \"predictalot\",\n  \"version\": \"1.2.2\",\n  \"publishedAt\": 1791644446805\n}\n\nFile v1.2.2:references/setup.md\n\n# predictalot setup\n\nConsumer-facing reference for running / pointing at a predictalot instance. This skill talks to an instance you already run and trust; the notes below cover standing one up if you haven't.\n\n## Requirements\n\n- Docker\n- Optional: NVIDIA GPU + NVIDIA Container Toolkit for the CUDA image (CPU works for all five FMs; `chronos-2` is the fastest on CPU)\n- Disk for model snapshots under `/models` (per-slug sizes ≈ chronos-2 ~120 MB, timesfm-2.5 ~200 MB, moirai-2 ~50 MB, toto-1 ~580 MB, sundial-base-128m ~490 MB) plus trained tabular models under `/models/tabular/<id>/`\n\n## Security & safety\n\npredictalot is a plain HTTP + MCP service — anyone who can reach the port can call it. Before any non-local / shared deployment:\n\n- Set `PREDICTALOT_AUTH_TOKENS` to a strong generated secret, e.g. `$(openssl rand -hex 32)` — never leave it at a placeholder/default value.\n- Bind to loopback by default (`-p 127.0.0.1:8080:8080`); only expose beyond loopback behind a reverse proxy / VPN (see \"Public Access via Reverse Proxy\" below).\n\n## Quick Install\n\n### CPU\n\n```bash\ndocker run -d --name predictalot \\\n  -v $HOME/predictalot-models:/models \\\n  -e PREDICTALOT_AUTH_TOKENS=$(openssl rand -hex 32) \\\n  -p 127.0.0.1:8080:8080 \\\n  psyb0t/predictalot:latest\n```\n\n### CUDA\n\n```bash\ndocker run -d --name predictalot --gpus all \\\n  -v /srv/predictalot-models:/models \\\n  -e PREDICTALOT_AUTH_TOKENS=$(openssl rand -hex 32) \\\n  -e PREDICTALOT_DEVICE=cuda \\\n  -e PREDICTALOT_PRELOAD=chronos-2,toto-1,sundial-base-128m \\\n  -p 127.0.0.1:8080:8080 \\\n  psyb0t/predictalot:latest-cuda\n```\n\n`PREDICTALOT_DEVICE=auto` (the default) picks CUDA when available, else CPU.\n\n### docker-compose\n\n```yaml\nservices:\n  predictalot:\n    image: psyb0t/predictalot:latest\n    ports:\n      - \"127.0.0.1:8080:8080\"\n    environment:\n      PREDICTALOT_AUTH_TOKENS: \"${PREDICTALOT_AUTH_TOKENS:?set to a generated secret, e.g. openssl rand -hex 32}\"\n      PREDICTALOT_PRELOAD: chronos-2,toto-1\n    volumes:\n      - ./predictalot-models:/models\n    restart: unless-stopped\n```\n\nGenerate the token once and export it before `docker compose up` (e.g. `export PREDICTALOT_AUTH_TOKENS=$(openssl rand -hex 32)`) — never commit a real token or ship the example value as-is. The token MUST be changed before any non-local/shared deployment.\n\n**Verify:** `curl http://localhost:8080/healthz` returns `{\"ok\": true}` once boot is done.\n\n**Snapshots:** foundation-model weights download into `/models/<slug>/` on first use (or at boot when listed in `PREDICTALOT_PREFETCH`). Bind-mount `/models` so restarts are no-ops. Trained tabular models persist under `/models/tabular/<id>/`.\n\n## Environment Variables\n\nAll runtime config is `PREDICTALOT_*` — set via `docker run -e`, compose `environment:`, or a k8s ConfigMap.\n\n### Auth + bind\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_AUTH_TOKENS` | (empty) | Comma-separated bearer tokens. Empty = **refused at startup** unless `PREDICTALOT_ALLOW_NO_AUTH=1`. When set, `Authorization: Bearer <token>` (or `?apiToken=<token>`) required on every `/v1/*` and `/mcp` request. |\n| `PREDICTALOT_ALLOW_NO_AUTH` | `0` | Explicit opt-in to run with no tokens (open auth). Required to start with an empty token list. |\n| `PREDICTALOT_HOST` | `0.0.0.0` | uvicorn bind host. |\n| `PREDICTALOT_PORT` | `8080` | uvicorn bind port (inside the container). |\n\nControl network exposure at `docker run` time:\n- `-p 127.0.0.1:8080:8080` — loopback only on the host.\n- `-p 8080:8080` — all host interfaces.\n\n### Device + model registry\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_DEVICE` | `auto` | `auto` / `cpu` / `cuda` / `cuda:N`. |\n| `PREDICTALOT_MODEL_DIR` | `/models` | Where snapshot dirs land (and tabular models: `/models/tabular/<id>/`). **Bind-mount this** to persist. |\n| `PREDICTALOT_PREFETCH` | (empty) | Comma-separated slugs or `all` — downloaded at container start before uvicorn boots. |\n| `PREDICTALOT_PRELOAD` | (empty) | Comma-separated slugs loaded into memory at boot (skips first-call cold load). |\n\n### Lifecycle (idle unloading)\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_MODEL_IDLE_TIMEOUT` | `30m` | Idle time before a loaded FM is unloaded. Go-style durations (`30m`, `1h`, `1d2h3m`). `0` disables auto-unload. |\n| `PREDICTALOT_MODEL_IDLE_TIMEOUT_<SLUG>` | inherits global | Per-model override. Slug normalized: uppercase + `-`/`.` → `_` (e.g. `PREDICTALOT_MODEL_IDLE_TIMEOUT_MOIRAI_2`). |\n\nA background sweeper runs every 60 s and unloads models idle past their timeout.\n\n### Limits + per-model caps\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_MAX_BODY_SIZE` | `32mb` | Cap on request body — over → 413. Human-readable (`32mb`, `512k`, `1g`) or plain int bytes. |\n| `PREDICTALOT_TIMESFM_MAX_CONTEXT` | `2048` | Compile-time max context for TimesFM. Multiple of 32. |\n| `PREDICTALOT_TIMESFM_MAX_HORIZON` | `512` | Compile-time max horizon for TimesFM. Multiple of 128. |\n| `PREDICTALOT_MOIRAI_MAX_CONTEXT` | `4000` | Wrapper context length for Moirai-2 (shorter inputs zero-padded). |\n| `PREDICTALOT_MOIRAI_MAX_HORIZON` | `512` | Upper horizon for Moirai-2. Wrappers are cached per requested horizon. Multivariate requests have a 64-step native limit. |\n\n### Sundial sidecar + logging\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_SUNDIAL_SOCK` | `/tmp/predictalot/sundial.sock` | Unix socket the main service uses to reach the sundial sidecar venv. |\n| `PREDICTALOT_SUNDIAL_NUM_SAMPLES` | `64` | Monte-Carlo samples per sundial forecast (more = smoother quantiles, linearly slower). |\n| `PREDICTALOT_SUNDIAL_READY_TIMEOUT` | `60s` | How long to wait for the sundial sidecar on first request. |\n| `PREDICTALOT_LOG_LEVEL` | `INFO` | `DEBUG` / `INFO` / `WARNING` / `ERROR`. |\n\n## Ports\n\n| Port | Service |\n|---|---|\n| 8080 | HTTP API + MCP (`/mcp`) on the same port |\n\nContainer binds `0.0.0.0:8080` by default. Use `-p` at `docker run` for whatever host mapping you want.\n\n## Management\n\n```bash\ndocker logs -f predictalot            # tail logs (sundial worker stderr tagged [sundial])\ndocker stop predictalot               # stop\ndocker rm predictalot                 # remove\ndocker pull psyb0t/predictalot:latest # update\n```\n\nInspect model state without stopping anything:\n\n```bash\ncurl -s http://localhost:8080/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n```\n\nFree every resident foundation model early when the next task does not need it:\n\n```bash\ncurl -s -X POST http://localhost:8080/v1/models/unload \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n```\n\nThe endpoint returns `409` while a foundation forecast is active. A forecast body with `\"unload\": true` instead schedules that model for teardown after concurrent forecasts using the same model have finished.\n\n## OpenClaw / ClawHub Config\n\n```bash\nexport PREDICTALOT_URL=http://localhost:8080\nexport PREDICTALOT_AUTH_TOKEN=<token>   # only if the server requires it\n```\n\nOr via `~/.openclaw/openclaw.json`:\n\n```json\n{\n  \"skills\": {\n    \"entries\": {\n      \"predictalot\": {\n        \"env\": {\n          \"PREDICTALOT_URL\": \"http://localhost:8080\",\n          \"PREDICTALOT_AUTH_TOKEN\": \"<token>\"\n        }\n      }\n    }\n  }\n}\n```\n\n## Public Access via Reverse Proxy (optional)\n\nFor public exposure, terminate TLS at a reverse proxy (Caddy / Traefik / nginx) and combine it with `PREDICTALOT_AUTH_TOKENS`.\n\n```caddy\npredictalot.example.com {\n    reverse_proxy localhost:8080\n}\n```\n\nSet the auth token on the container so even a misconfigured proxy still requires `Authorization: Bearer`. Don't rely on the proxy alone. The same logic applies to Cloudflare Tunnel / Tailscale — the tunnel provides transport security, the bearer token provides app-layer auth.\n\nFile v1.2.2:skill-card.md\n\n## Description:\n\nHelps agents forecast time series and train or query tabular prediction models through a user-configured, self-hosted predictalot service.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[psyb0t](https://clawhub.ai/user/psyb0t)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and analysts use this skill to request forecasts, uncertainty bands, and sample paths from a trusted predictalot endpoint, or to train and query tabular prediction models on their own features.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Forecasting and training data are sent to the configured service.\n\nMitigation: Use only an endpoint you control and trust; do not send proprietary data to an untrusted URL.\n\nRisk: A publicly reachable service can expose forecasting and model-management operations.\n\nMitigation: Keep the service bound to localhost unless protected by TLS, a strong bearer token, and network access controls.\n\nRisk: An unpinned Docker image can change between deployments.\n\nMitigation: Pin the image to a reviewed version or digest before production use.\n\nRisk: Deleting a stored tabular model is irreversible.\n\nMitigation: Verify the model ID and obtain explicit confirmation before deletion.\n\n## Reference(s):\n\n- [predictalot on ClawHub](https://clawhub.ai/psyb0t/skills/predictalot)\n- [Setup and security guidance](references/setup.md)\n- [Model Context Protocol documentation](https://modelcontextprotocol.io)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Configuration guidance]\n\n**Output Format:** [Markdown guidance, shell commands, and JSON forecast responses]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Forecasts may include quantile bands or sample paths; tabular predictions depend on the selected mode.]\n\n## Skill Version(s):\n\n1.2.2 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.2.1: 5 files, 17885 bytes\n\nFiles: references/setup.md (7810b), scripts/predictalot.sh (2122b), skill-card.md (2211b), SKILL.md (34632b), _meta.json (130b)\n\nFile v1.2.1:SKILL.md\n\n---\nname: predictalot\ndescription: Self-hosted forecasting/prediction service. Foundation time-series endpoints under /v1/timeseries/<type>/{forecast,forecast/ensemble} + GET .../models — univariate, past/future/both covariates, multivariate, samples — over 5 zero-shot models (chronos-2, timesfm-2.5, moirai-2, toto-1, sundial-base-128m). Plus supervised tabular ML under /v1/tabular/ (9 backends — lightgbm, xgboost, hist-gbt, random-forest, logistic, mlp, svm-rbf, knn, naive-bayes — over direction/value/quantile modes) with train+persist, weighted ensembles, and calibrated/stacking/diversified meta-learners. Unified REST + MCP (streamable-HTTP at /mcp, one tool per (type, model) cell + per-type ensemble + listing) + optional bearer auth. Use when the user wants to forecast a numeric time series (quantile bands or raw sample paths), condition a forecast on known/future covariates, ensemble several forecasters, or train a tabular model on engineered features and predict direction/value/quantiles on the latest snapshot.\nhomepage: https://github.com/psyb0t/docker-predictalot\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🔮\", \"primaryEnv\": \"PREDICTALOT_URL\", \"requires\": { \"bins\": [\"docker\", \"curl\"] } } }\npermissions:\n  network: \"outbound HTTP(S) to the configured PREDICTALOT_URL only (forecast/train/model-management calls + MCP at /mcp)\"\n  shell: \"docker + curl invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# predictalot\n\nSelf-hosted forecasting service — one HTTP container, two model families.\n\n- **Foundation time-series (zero-shot)** — 5 forecasters (`chronos-2`, `timesfm-2.5`, `moirai-2`, `toto-1`, `sundial-base-128m`). Hand them a context window, get quantile bands or raw sample paths. No training step. Routed by forecast type under `/v1/timeseries/<type>/` — `univariate`, `covariates/past`, `covariates/future`, `covariates` (past+future), `multivariate`, `samples`. Each type has `forecast`, `forecast/ensemble`, and a `models` listing.\n- **Tabular ML (supervised)** — 9 learners (`lightgbm`, `xgboost`, `hist-gbt`, `random-forest`, `logistic`, `mlp`, `svm-rbf`, `knn`, `naive-bayes`), all supporting 3 modes (`direction` / `value` / `quantile`). Train on YOUR engineered features, persist server-side by `modelId`, forecast on the latest feature snapshot. Weighted ensembles over stored models plus 3 meta-learners (`calibrated` / `stacking` / `diversified`). Under `/v1/tabular/`.\n- **MCP** — streamable-HTTP tools at `/mcp`. One tool per (FM type, model) cell plus per-type ensemble + listing. Tabular is HTTP-only.\n- **Model lifecycle** — `POST /v1/models/unload` releases every resident foundation model. A forecast's `unload: true` waits for concurrent forecasts using that model, then tears it down. The MCP equivalent is `unload_models`.\n- **Auth** — optional bearer token (`PREDICTALOT_AUTH_TOKENS` on the server; refuses to start with no tokens unless `PREDICTALOT_ALLOW_NO_AUTH=1`).\n\nFor installation, configuration, and container setup, see [references/setup.md](references/setup.md).\n\n## Security & safety\n\n- **Network-exposed** — predictalot is a plain HTTP + MCP service; anyone who can reach the port can call it. Set `PREDICTALOT_AUTH_TOKENS` to a strong generated secret (`$(openssl rand -hex 32)`) and bind to loopback (`-p 127.0.0.1:8080:8080`) by default. Only expose beyond loopback behind a reverse proxy / VPN, and never with the default/example token.\n- **External transmission** — every forecast/train call sends your time series, engineered feature values, and/or `modelId`s to whatever `PREDICTALOT_URL` points at — that data leaves your host. Point it only at a service you run or explicitly trust; prefer HTTPS.\n- **Consumer-only** — this skill talks to an instance you already run and trusts. It never provisions, starts, or hardens the server; that's the operator's job (see setup.md).\n- **Destructive delete requires confirmation** — `DELETE /v1/tabular/models/{modelId}` permanently removes a trained model and cannot be undone. Only call it against a `modelId` you obtained from a prior `GET /v1/tabular/models` (or a train response) in this session, and get explicit user confirmation before issuing the delete.\n\n## When To Use\n\n- Forecast a numeric time series N steps ahead and get calibrated quantile bands (`0.1`/`0.5`/`0.9`, etc.) — zero-shot, no training.\n- Get raw Monte-Carlo sample paths (via the `samples` type) to compute custom risk metrics / joint distributions across horizon steps.\n- Condition a forecast on covariates: known-history (`covariates/past`), forward-known drivers like a planned promotion or price schedule (`covariates/future`), or both at once (`covariates`).\n- Forecast several correlated channels jointly (`multivariate`).\n- Combine multiple forecasters into a weighted ensemble and inspect each member's individual forecast + applied weight.\n- Train a supervised model on engineered features and predict next-bar direction (`P(up)`), a point value, or quantiles on the latest snapshot.\n- Combine trained tabular models via ensemble or a `calibrated` / `stacking` / `diversified` meta-learner.\n\n## When NOT To Use\n\n- Real-time / streaming forecasts — every endpoint is request/response only.\n- Automatic feature engineering on the tabular side — you supply the features; predictalot does not derive indicators or lags for you.\n- `timesfm-2.5` for accuracy — it is the weakest of the five on every benchmarked dataset. Skip it or set `weights={\"timesfm-2.5\": 0}` in ensembles.\n- Commercial use of `moirai-2` — it ships under CC-BY-NC-4.0 (non-commercial). The other four FMs are Apache 2.0.\n- Covariate/multivariate/samples types on models that don't support them — membership is fixed per type (see below). A non-member `model` → 400.\n- Provisioning or hardening the server from here — this skill is a **consumer**. It talks to an instance the user already runs and trusts; it never launches, escalates, or reconfigures the container.\n\n## Setup\n\nThe container should already be running. Point at it:\n\n```bash\nexport PREDICTALOT_URL=http://localhost:8080\n```\n\nIf the server has `PREDICTALOT_AUTH_TOKENS` set, export a token too:\n\n```bash\nexport PREDICTALOT_AUTH_TOKEN=<your-token>\n# every /v1/* and /mcp request below needs: -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\"\n```\n\n**Verify:** `curl $PREDICTALOT_URL/healthz` returns `{\"ok\": true}`. (`/healthz` is unauthenticated.)\n\nFor install / configuration / env vars / CPU vs CUDA images, see [references/setup.md](references/setup.md).\n\n## Models & Types\n\nFoundation models, and which forecast types each supports (a `model` outside a type's member set → 400):\n\n| Model | Univariate | Multivariate | Cov: past | Cov: future | Cov: both | Samples | License | Recommended for |\n|---|:-:|:-:|:-:|:-:|:-:|:-:|---|---|\n| `chronos-2` | ✓ | ✓ | ✓ | ✓ | ✓ | — | Apache 2.0 | Default all-rounder; fastest on CPU; only model with future/both covariates. |\n| `timesfm-2.5` | ✓ | — | — | — | — | — | Apache 2.0 | Weakest on benchmarks — skip or zero-weight it. Univariate-only; compile-time horizon cap. |\n| `moirai-2` | ✓ | ✓ | ✓ | — | — | — | CC-BY-NC-4.0 | Clean cyclic/seasonal series; correlated channels. Non-commercial license. |\n| `toto-1` | ✓ | ✓ | — | — | — | ✓ | Apache 2.0 | Noisy / observability / financial series; exposes raw sample paths. |\n| `sundial-base-128m` | ✓ | — | — | — | — | ✓ | Apache 2.0 | Drifting / trending series; generative sample paths. Runs in a sidecar venv. |\n\nType → URL prefix:\n\n| Type | URL prefix | Members |\n|---|---|---|\n| univariate | `/v1/timeseries/univariate` | chronos-2, timesfm-2.5, moirai-2, toto-1, sundial-base-128m |\n| multivariate | `/v1/timeseries/multivariate` | chronos-2, moirai-2, toto-1 |\n| covariates (past) | `/v1/timeseries/covariates/past` | chronos-2, moirai-2 |\n| covariates (future) | `/v1/timeseries/covariates/future` | chronos-2 |\n| covariates (past+future) | `/v1/timeseries/covariates` | chronos-2 |\n| samples | `/v1/timeseries/samples` | toto-1, sundial-base-128m |\n\nDiscover live per-type membership + runtime state with `GET /v1/timeseries/<type>/models`. Discover tabular backends with `GET /v1/tabular/backends`.\n\nTabular backends (all support `direction` / `value` / `quantile`):\n\n| Slug | Display name | Category |\n|---|---|---|\n| `lightgbm` | LightGBM | boosting |\n| `xgboost` | XGBoost | boosting |\n| `hist-gbt` | HistGradientBoosting (sklearn) | boosting |\n| `random-forest` | Random Forest | bagging |\n| `logistic` | Logistic / Ridge / QuantileRegressor (linear baselines) | linear |\n| `mlp` | Multi-Layer Perceptron (sklearn) | neural |\n| `svm-rbf` | SVM with RBF kernel | kernel |\n| `knn` | k-Nearest Neighbors | distance |\n| `naive-bayes` | Gaussian Naive Bayes / BayesianRidge | independence |\n\n## Quick Start\n\n> Every forecast/train call below sends your time series, engineered features, and/or `modelId`s over the network to `$PREDICTALOT_URL`. Only point this at a trusted, self-hosted instance you control, prefer HTTPS, treat proprietary datasets as sensitive, and never echo `PREDICTALOT_AUTH_TOKEN` in output.\n\n```bash\n# Health (unauthenticated).\ncurl -s $PREDICTALOT_URL/healthz | jq\n\n# List univariate models + their runtime state.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n\n# Zero-shot univariate forecast: 5 steps ahead of one series, chronos-2.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n        \"model\": \"chronos-2\",\n        \"context\": [[10,11,12,13,14,15,16,17,18,19,20]],\n        \"config\": { \"horizon\": 5, \"quantileLevels\": [0.1, 0.5, 0.9] }\n      }' | jq\n```\n\nWire format is **camelCase** (`quantileLevels`, `contextLength`, `pastCovariates`, `futureCovariates`, `numSamples`, `memberOverrides`, `modelId`). All `/v1/*` routes require the bearer header when the server has tokens configured; drop the header for an open-auth deployment.\n\nShapes recur across the timeseries API: `context` is `[series][time]` (a batch of independent series), `horizon` > 0, `quantileLevels` is a subset of `{0.1, 0.2, …, 0.9}` (default `[0.1, 0.5, 0.9]`), `contextLength` caps history fed to the model (omit → per-model default), `unload: true` tears the model down after the response. `median` and `quantiles[\"0.5\"]` are the same for most models but can differ for chronos-2 (its `median` is the distribution mean).\n\nRelease every resident foundation model when the next task will not need one:\n\n```bash\ncurl -s -X POST $PREDICTALOT_URL/v1/models/unload \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n```\n\nThe request returns `409` while a foundation forecast is active. Do not retry it blindly. Wait for the forecast result, then retry when memory needs to be freed.\n\n---\n\n## API — `POST /v1/timeseries/univariate/forecast`\n\nSingle-model quantile forecast over a batch of independent series. `context` (your time series data) transmits off-box to `$PREDICTALOT_URL` — same data-transfer note as Quick Start applies to every timeseries endpoint below.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `model` | yes | — | One of the univariate members. Not a member → 400; unknown slug → 404. |\n| `context` | yes | — | `[series][time]` — one inner list of floats per series. |\n| `config.horizon` | yes | — | Steps ahead. Must be `> 0` (else 422). |\n| `config.quantileLevels` | no | `[0.1, 0.5, 0.9]` | Subset of `{0.1..0.9}` step 0.1. Out-of-range → 400. |\n| `config.contextLength` | no | per-model | Max history points fed to the model. `> 0`. |\n| `config.extra` | no | — | Per-backend escape-hatch dict, camelCased. Unknown keys ignored. E.g. chronos-2: `batchSize`, `crossLearning`, `limitPredictionLength`; toto-1: `numSamples`, `samplesPerBatch`, `useKvCache`; timesfm-2.5: `normalizeInputs`, `fixQuantileCrossing`, … |\n| `unload` | no | `false` | Unload the model after responding. |\n\n### Response\n\n```json\n{\n  \"model\": \"chronos-2\",\n  \"horizon\": 5,\n  \"quantileLevels\": [0.1, 0.5, 0.9],\n  \"median\": [[20.9, 21.8, 22.7, 23.6, 24.5]],\n  \"quantiles\": {\n    \"0.1\": [[20.1, 20.8, 21.5, 22.1, 22.7]],\n    \"0.5\": [[20.9, 21.8, 22.7, 23.6, 24.5]],\n    \"0.9\": [[21.7, 22.9, 24.0, 25.1, 26.3]]\n  }\n}\n```\n\n`median` is `[series][time]`; `quantiles` maps each level string → `[series][time]`.\n\n### Error Contract\n\n| Status | Shape | When |\n|---|---|---|\n| 200 | forecast JSON | success |\n| 400 | `{\"detail\": \"...\"}` | empty context/series, model not a member of the type, quantile level outside `{0.1..0.9}`, horizon over a model's compile-time cap (timesfm-2.5 / moirai-2) |\n| 401 | `{\"detail\": \"...\"}` | tokens configured, missing/wrong bearer |\n| 404 | `{\"detail\": \"...\"}` | unknown `model` slug |\n| 413 | `{\"detail\": \"...\"}` | body over `PREDICTALOT_MAX_BODY_SIZE` |\n| 422 | `{\"detail\": [...]}` | Pydantic validation (missing/typed fields, `horizon` not `> 0`) |\n| 503 | `{\"detail\": \"...\"}` | snapshot download failed, inference threw, or sundial sidecar unreachable |\n\n---\n\n## API — `POST /v1/timeseries/univariate/forecast/ensemble`\n\nWeighted mean across every univariate member. No `model` field — membership is the whole type.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `context` | yes | — | `[series][time]`. |\n| `config` | yes | — | Same `ForecastConfig` as single forecast (`horizon`, `quantileLevels`, `contextLength`, `extra`). |\n| `weights` | no | uniform | `{slug: float ≥ 0}`. Missing slugs default to 1.0; weight `0` disables a member; unknown slug → 400. |\n| `memberOverrides` | no | — | `{slug: partial-config}` — per-member overrides of the global `config` (e.g. give one member a different `contextLength` or `extra`). |\n| `unload` | no | `false` | Unload members after responding. |\n\n### Response\n\nAggregated `median` + `quantiles` (same shapes as single forecast) with `model: \"ensemble\"`, plus:\n\n```json\n{\n  \"model\": \"ensemble\",\n  \"horizon\": 5,\n  \"quantileLevels\": [0.1, 0.5, 0.9],\n  \"median\": [[...]],\n  \"quantiles\": { \"0.1\": [[...]], \"0.5\": [[...]], \"0.9\": [[...]] },\n  \"ensembleMembers\": [\"chronos-2\", \"moirai-2\", \"toto-1\"],\n  \"weights\": { \"chronos-2\": 0.5, \"moirai-2\": 0.25, \"toto-1\": 0.25 },\n  \"individual\": {\n    \"chronos-2\": { \"model\": \"chronos-2\", \"horizon\": 5, \"quantileLevels\": [...],\n                   \"median\": [[...]], \"quantiles\": {...}, \"weight\": 0.5 }\n  }\n}\n```\n\n`individual[slug]` is that member's full single-forecast response + its applied `weight`.\n\n### Error Contract\n\nSame as single univariate, minus 404 (no `model` field) — an unknown `weights` slug is a 400, not 404.\n\n---\n\n## API — `POST /v1/timeseries/covariates/past/forecast`\n\nForecast each target series conditioned on covariates known only up to `t`. Members: `chronos-2`, `moirai-2`.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `model` | yes | — | A past-covariate member. |\n| `context` | yes | — | `[series][time]` — the univariate targets. |\n| `pastCovariates` | yes | — | `list[dict[name, float[]]]` — one mapping per series; every value array is the **same length as that series' context**. All series share the same covariate names. |\n| `config` | yes | — | `ForecastConfig` (`horizon`, `quantileLevels`, `contextLength`, `extra`). |\n| `unload` | no | `false` | |\n\n### Response\n\nIdentical shape to univariate single forecast (`model`, `horizon`, `quantileLevels`, `median`, `quantiles`).\n\n### Error Contract\n\nSame table as univariate, plus 400 when a `pastCovariates` value length doesn't match the matching `context` series.\n\n---\n\n## API — `POST /v1/timeseries/covariates/past/forecast/ensemble`\n\nWeighted ensemble over the past-covariate members. Fields = past forecast fields plus `weights` + `memberOverrides` (as in the univariate ensemble). Response = univariate ensemble shape.\n\n---\n\n## API — `POST /v1/timeseries/covariates/future/forecast`\n\nForecast conditioned on covariates known only over the **future window** (length == `horizon`) — e.g. a planned promotion, a published price schedule, a weather forecast. Member: `chronos-2` only.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `model` | yes | — | `chronos-2`. |\n| `context` | yes | — | `[series][time]` targets. |\n| `futureCovariates` | yes | — | `list[dict[name, float[]]]` — one mapping per series; **each value array of length `horizon`**. All series share names. |\n| `config` | yes | — | `ForecastConfig`. |\n| `unload` | no | `false` | |\n\n### Response\n\nUnivariate single-forecast shape.\n\n### Error Contract\n\nUnivariate table + 400 when a `futureCovariates` value length ≠ `config.horizon`.\n\n---\n\n## API — `POST /v1/timeseries/covariates/future/forecast/ensemble`\n\nEnsemble over future-covariate members (currently just `chronos-2`, so only useful once more back it). Fields = future forecast + `weights` + `memberOverrides`. Response = univariate ensemble shape.\n\n---\n\n## API — `POST /v1/timeseries/covariates/forecast` (past + future)\n\nCombined past **and** future covariates in one call. Member: `chronos-2` only.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `model` | yes | — | `chronos-2`. |\n| `context` | yes | — | `[series][time]` targets. |\n| `pastCovariates` | yes | — | Per series, same length as context. |\n| `futureCovariates` | yes | — | Per series, length `horizon`. **Every future-covariate name MUST also appear in `pastCovariates`** for that series (chronos-2 constraint) — else 400. |\n| `config` | yes | — | `ForecastConfig`. |\n| `unload` | no | `false` | |\n\n### Response\n\nUnivariate single-forecast shape.\n\n### Error Contract\n\nUnivariate table + 400 for length mismatches or a future-covariate name missing from `pastCovariates`.\n\n---\n\n## API — `POST /v1/timeseries/covariates/forecast/ensemble`\n\nEnsemble over past+future members. Fields = past+future forecast + `weights` + `memberOverrides`. Response = univariate ensemble shape.\n\n---\n\n## API — `GET /v1/timeseries/<type>/models`\n\nPer-type model listing + runtime state. `<type>` ∈ `univariate`, `multivariate`, `covariates/past`, `covariates/future`, `covariates`, `samples`.\n\n### Response\n\n```json\n{\n  \"type\": \"univariate\",\n  \"models\": [\n    { \"slug\": \"chronos-2\", \"loaded\": true, \"lastUsedSecsAgo\": 12.4, \"idleTimeoutSecs\": 1800.0 },\n    { \"slug\": \"timesfm-2.5\", \"loaded\": false, \"lastUsedSecsAgo\": null, \"idleTimeoutSecs\": 1800.0 }\n  ]\n}\n```\n\n`lastUsedSecsAgo` is `null` when the model has never loaded. `idleTimeoutSecs` reflects `PREDICTALOT_MODEL_IDLE_TIMEOUT` (or a per-slug override); `0` means never auto-unloaded.\n\n### Error Contract\n\n200 on success; 401 when tokens are configured and the bearer is missing/wrong.\n\n---\n\n## API — `GET /v1/tabular/backends`\n\nList the supervised tabular backends and the modes each supports. Use this to discover the live set before a train call.\n\n### Response\n\n```json\n{\n  \"backends\": [\n    { \"slug\": \"lightgbm\", \"displayName\": \"LightGBM\", \"category\": \"boosting\",\n      \"supportedModes\": [\"direction\", \"quantile\", \"value\"] },\n    { \"slug\": \"xgboost\", \"displayName\": \"XGBoost\", \"category\": \"boosting\",\n      \"supportedModes\": [\"direction\", \"quantile\", \"value\"] }\n  ]\n}\n```\n\n### Error Contract\n\n200 on success; 401 when tokens are configured and the bearer is missing/wrong.\n\n---\n\n## Tabular API (train → forecast)\n\nThe tabular side trains on YOUR engineered features and persists a model server-side by `modelId`. `target` / `features` are generic float lists — no OHLC / indicator assumptions. `features` is per series: `featureName → time-aligned float list`. Same data-transfer note as Quick Start applies: `target`/`features`/`modelId` all transmit to `$PREDICTALOT_URL`.\n\n### `POST /v1/tabular/train`\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `modelId` | yes | — | Caller-chosen id, used for storage + later forecast lookup. |\n| `backend` | yes | — | A slug from `GET /v1/tabular/backends`. Unknown → 404. |\n| `target` | yes | — | `[series][time]` — the scalar series to predict. Empty → 400. |\n| `features` | yes | — | `list[dict[name, float[]]]` — same length + names per series as `target`. Count must match `target` (else 400); differing key sets across series → 400. |\n| `config.mode` | yes | — | `direction` (sign of `target[t+h] - target[t]`) / `value` (regress `target[t+h]`) / `quantile`. Mode not supported by the backend → 400. |\n| `config.horizon` | yes | — | Bars ahead (`t+h`). `> 0`. |\n| `config.quantileLevels` | when `quantile` | — | Required for `mode=\"quantile\"`; subset of `{0.1..0.9}`. |\n| `config.*` (tier-2) | no | — | `nEstimators`, `maxDepth`, `learningRate`, `numLeaves`, `minSamples`, `randomState`, `categoricalFeatures`, `monotonicConstraints`, `classWeight`, `sampleWeight`, `earlyStoppingRounds`, `validationFraction`. Backends ignore knobs they don't use. |\n| `config.extra` | no | — | Per-backend hyperparams (e.g. svm-rbf reads `C`/`gamma`; mlp reads `hiddenLayerSizes`/`activation`; knn reads `nNeighbors`/`weights`/`metric`). |\n| `overwrite` | no | `false` | Reuse an existing `modelId` → 409 unless `true`. |\n\n**Response** (`TrainResponse`): `modelId`, `backend`, `mode`, `horizon`, `nTrainingRows`, `nFeatures`, `featureNames`, `featureImportance` (`{name: float}`), `trainSecs`.\n\n### `POST /v1/tabular/forecast`\n\nRuns a stored model on the **latest** feature snapshot — the LAST row per series is the anchor; earlier rows are ignored.\n\n| Field | Required | Notes |\n|---|---|---|\n| `modelId` | yes | Missing → 404. If its backend is no longer registered → 410. |\n| `features` | yes | `list[dict[name, float[]]]`; names must match training. Missing a trained name → 400. |\n\n**Response** (`ForecastResponse`) is mode-dependent:\n- `direction`: `probUp: float[]` (per series) + `confidence: float[]` (`|probUp-0.5|*2`).\n- `value`: `predicted: float[]`.\n- `quantile`: `median: float[series][1]` + `quantiles: {level: float[series][1]}`.\n\n### `POST /v1/tabular/forecast/ensemble`\n\nWeighted combination of several stored models on the same features; all members must share `mode`, `horizon`, and `featureNames` (mismatch → 400).\n\n| Field | Required | Notes |\n|---|---|---|\n| `modelIds` | yes | Stored ids to combine (≥ 1). Any missing → 404. |\n| `weights` | no | `{modelId: float ≥ 0}`. None → uniform; `0` removes a member; unknown id or negative → 400. |\n| `features` | yes | As in forecast; last row per series is the anchor. |\n\n**Response** (`EnsembleForecastResponse`): `mode`, `horizon`, `ensembleMembers`, normalized `weights`, `individual` (`{modelId: member response}`), plus the combined mode-specific fields (`probUp`+`confidence` / `predicted` / `median`+`quantiles`).\n\n### `GET /v1/tabular/models` and `DELETE /v1/tabular/models/{modelId}` (destructive)\n\n`GET` lists stored models: `{\"models\": [{ modelId, backend, mode, horizon, nFeatures, featureNames, nTrainingRows, trainedAtUnix }]}`. `DELETE` removes one (`{\"modelId\": \"...\", \"removed\": true}`; 404 if absent) — **irreversible**. Only delete a `modelId` returned by a prior `GET /v1/tabular/models` call (or a train response), and get explicit user confirmation first.\n\n### Meta-learners\n\nComposite endpoints that hold out / cross-validate internally so you don't have to orchestrate it client-side. Each persists one blob under a `meta:<kind>` backend tag and forecasts via `POST /v1/tabular/forecast/<kind>` with `{modelId, features}`.\n\n- `POST /v1/tabular/train/calibrated` — base learner + post-hoc probability calibrator. **`direction` only.** Fields: `modelId`, `baseBackend`, `target`, `features`, `config` (mode must be `direction`), `calibrationMethod` (`sigmoid`|`isotonic`, default `sigmoid`), `calibrationFraction` (default `0.2`, in `(0,1)`), `overwrite`. Too-small split → 400.\n- `POST /v1/tabular/train/stacking` — K base learners + a meta-learner on K-fold OOF predictions. **`direction` only (v1).** Fields: `modelId`, `members` (≥ 2 `{backend, config}`, all `direction`, horizon == top-level), `metaBackend` (default `logistic`), `target`, `features`, `horizon`, `nFolds` (2–10, default 5), `overwrite`. Needs `≥ nFolds*5` rows. Response includes `oofScore` (AUC).\n- `POST /v1/tabular/train/diversified` — trains K candidates, selects a low-correlation subset by OOF Pearson correlation, combines equal-weight. Supports all 3 modes. Fields: `modelId`, `candidates` (≥ 2, mode+horizon must match top-level), `target`, `features`, `horizon`, `mode`, `quantileLevels` (required if `quantile`), `nFolds` (2–10, default 3), `maxPairwiseCorr` (0–1, default 0.85), `minMembers`, `maxMembers`, `overwrite`. Response includes `candidateCorr`.\n\nMeta-train responses (`MetaTrainResponse`): `modelId`, `kind`, `mode`, `horizon`, `membersUsed`, `nTrainingRows`, `nFeatures`, `featureNames`, `trainSecs`, plus `oofScore` (stacking) / `candidateCorr` (diversified). Meta-forecast responses (`MetaForecastResponse`): `modelId`, `kind`, `mode`, `horizon`, `members` (per-member breakdown), `selectedMembers` (diversified), and the mode-specific combined fields.\n\n### Tabular Error Contract\n\n| Status | When |\n|---|---|\n| 200 | success |\n| 400 | empty `target`; `target`/`features` count mismatch; differing feature key sets; mode unsupported by backend; `quantile` without `quantileLevels`; forecast features missing a trained name; ensemble member mode/horizon/features mismatch; unknown/negative ensemble weight; meta constraint violated (calibrated non-direction, stacking non-direction, diversified quantile w/o levels, split too small) |\n| 401 | tokens configured, missing/wrong bearer |\n| 404 | unknown tabular `backend` on train; `modelId` not found on forecast/meta |\n| 409 | train with `overwrite: false` against an existing `modelId` |\n| 410 | forecast against a `modelId` whose backend is no longer registered |\n| 413 | body over `PREDICTALOT_MAX_BODY_SIZE` |\n| 422 | Pydantic validation |\n| 503 | training/inference threw |\n\n---\n\n## MCP Endpoint (`/mcp`)\n\npredictalot mounts a [Model Context Protocol](https://modelcontextprotocol.io) server over Streamable HTTP at `/mcp`, in the same process, behind the same bearer auth. The tool surface mirrors the **foundation-model** routes only — tabular is HTTP-only.\n\nFor each forecast type there are three classes of tool:\n- `forecast_<type>_<model>` — single-model forecast for one (type, model) cell. Types are underscore-normalized: `univariate`, `multivariate`, `covariates_past`, `covariates_future`, `covariates_both`, `samples`; model slugs are underscore-normalized too (`chronos-2` → `chronos_2`). E.g. `forecast_univariate_chronos_2`, `forecast_covariates_future_chronos_2`.\n- `forecast_<type>_ensemble` — weighted ensemble over every model supporting that type.\n- `list_<type>_models` — which models implement the type + runtime state (`{type, models: [{slug, loaded, lastUsedSecsAgo, idleTimeoutSecs}]}`).\n\nTool args mirror the HTTP bodies but flattened (no nested `config`): `context`, `horizon`, `quantile_levels?`, `context_length?`, `unload?`; covariate tools add `past_covariates` / `future_covariates`; ensemble tools add `weights?`; samples tools use `num_samples?` instead of quantile levels and return `samples` `[series][sample][time]` + `median`.\n\nWire it into Claude Code (auth optional — the token may also be passed as `?apiToken=<token>`):\n\n```bash\nclaude mcp add --transport http predictalot $PREDICTALOT_URL/mcp \\\n  --header \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\"\n```\n\n### Raw JSON-RPC\n\nThe transport requires `Accept: application/json, text/event-stream`.\n\n```bash\n# tools/list — enumerate every (type, model) tool + ensembles + listings.\ncurl -s $PREDICTALOT_URL/mcp/ \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/list\"}'\n\n# tools/call — univariate chronos-2 forecast.\ncurl -s $PREDICTALOT_URL/mcp/ \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"tools/call\",\n    \"params\": {\n      \"name\": \"forecast_univariate_chronos_2\",\n      \"arguments\": {\n        \"context\": [[10,11,12,13,14,15,16,17,18,19,20]],\n        \"horizon\": 5,\n        \"quantile_levels\": [0.1, 0.5, 0.9]\n      }\n    }\n  }'\n```\n\nEach tool returns a JSON-encoded string. User-input errors come back as `{\"error\": \"...\", \"context\": \"<type>/<model>\"}`; internal errors are redacted to `{\"error\": \"internal error; see server logs\", \"context\": \"...\"}`.\n\n## Bearer-Token Auth\n\nIf `PREDICTALOT_AUTH_TOKENS` (comma-separated list) is set on the server, every `/v1/*` and `/mcp` request needs `Authorization: Bearer <one-of-the-tokens>`; `/healthz` is always open. Missing/wrong → 401. Tokens are compared constant-time (`hmac.compare_digest`).\n\n```bash\nexport PREDICTALOT_AUTH_TOKEN=<your-token>\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n```\n\nThe server **refuses to start** with an empty token list unless it was launched with `PREDICTALOT_ALLOW_NO_AUTH=1` — in that open-auth mode there is no 401 and no header is needed. For untrusted networks, combine the token with a reverse proxy doing TLS + rate limiting.\n\n## Typical Workflows\n\n### Pick a model → forecast → read the intervals\n\n```bash\n# 1. See which univariate models are available + resident.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq -r '.models[].slug'\n\n# 2. Forecast with chronos-2 (default all-rounder), 12 steps, three bands.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\":\"chronos-2\",\"context\":[[100,102,101,105,110,108,112,115,120,118,125,130]],\n       \"config\":{\"horizon\":12,\"quantileLevels\":[0.1,0.5,0.9]}}' | jq\n\n# 3. Read the interval: quantiles[\"0.5\"] is the central path,\n#    [\"0.1\"]/[\"0.9\"] the 80% band — all shaped [series][time].\n```\n\n### Ensemble, zero-weighting the weak model\n\n```bash\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/forecast/ensemble \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"context\":[[100,102,101,105,110,108,112,115,120,118,125,130]],\n       \"config\":{\"horizon\":12},\n       \"weights\":{\"timesfm-2.5\":0,\"chronos-2\":2,\"toto-1\":1}}' | jq \\\n  '{members: .ensembleMembers, weights, median}'\n```\n\n### Future-covariate forecast (chronos-2)\n\n```bash\n# Target has 8 steps of history; horizon 4 → futureCovariates arrays length 4.\ncurl -s $PREDICTALOT_URL/v1/timeseries/covariates/future/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\":\"chronos-2\",\n       \"context\":[[20,22,21,25,24,27,29,31]],\n       \"futureCovariates\":[{\"promo\":[0,1,1,0]}],\n       \"config\":{\"horizon\":4}}' | jq\n```\n\n### Raw sample paths for custom risk metrics\n\n```bash\ncurl -s $PREDICTALOT_URL/v1/timeseries/samples/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\":\"toto-1\",\n       \"context\":[[100,102,101,105,110,108,112,115,120,118,125,130]],\n       \"config\":{\"horizon\":10,\"numSamples\":256}}' | jq '{numSamples, shape: (.samples[0]|length)}'\n# samples is [series][sample][time]; compute your own VaR / quantiles off the draws.\n```\n\n### Train a tabular direction model, then forecast the latest snapshot\n\n```bash\n# 1. Train on engineered features (you supply them).\ncurl -s $PREDICTALOT_URL/v1/tabular/train \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"modelId\":\"trend-3\",\"backend\":\"lightgbm\",\n       \"target\":[[100,101,99,102,105,103,107,110,108,112]],\n       \"features\":[{\"rsi\":[55,58,52,60,63,59,65,70,66,72],\n                    \"mom\":[0.2,0.3,-0.1,0.4,0.5,0.2,0.6,0.7,0.4,0.8]}],\n       \"config\":{\"mode\":\"direction\",\"horizon\":3,\"nEstimators\":400}}' | jq\n\n# 2. Forecast — LAST feature row is the anchor.\ncurl -s $PREDICTALOT_URL/v1/tabular/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"modelId\":\"trend-3\",\"features\":[{\"rsi\":[72],\"mom\":[0.8]}]}' | jq\n# → {\"modelId\":\"trend-3\",\"backend\":\"lightgbm\",\"mode\":\"direction\",\"horizon\":3,\n#    \"probUp\":[0.63],\"confidence\":[0.26]}\n```\n\nFor a driver that runs a univariate forecast and pretty-prints the interval, see [`scripts/predictalot.sh`](scripts/predictalot.sh):\n\n```bash\nPREDICTALOT_URL=http://localhost:8080 \\\nPREDICTALOT_AUTH_TOKEN=<token> \\\n  bash scripts/predictalot.sh chronos-2 5 10 11 12 13 14 15 16 17 18 19 20\n```\n\n## Tips\n\n1. **Default to `chronos-2`** — fastest on CPU, widest type coverage (only model with future/both covariates), solid general-purpose accuracy.\n2. **Skip or zero-weight `timesfm-2.5`** — weakest on every benchmark. In ensembles pass `weights={\"timesfm-2.5\":0}`.\n3. **`moirai-2` is CC-BY-NC-4.0** — non-commercial. Fine for research/eval, not for a commercial product.\n4. **Use `samples` for risk work** — `toto-1` / `sundial-base-128m` return raw `[series][sample][time]` paths; compute your own VaR / joint metrics instead of trusting fixed quantile cuts.\n5. **`median` ≠ `quantiles[\"0.5\"]` for chronos-2** — its `median` is the distribution mean; the others agree.\n6. **Covariate length rules** — past covariates match the context length; future covariates match `horizon`; in the past+future type every future-cov name must also be a past-cov name.\n7. **Tabular features are yours** — the API does no feature engineering. The LAST row per series is the forecast anchor.\n8. **`overwrite: false` is the default** on every train endpoint — re-training a known `modelId` is a 409 until you pass `overwrite: true`.\n9. **Discover, don't assume** — `GET /v1/timeseries/<type>/models` and `GET /v1/tabular/backends` are the live source of truth for membership and modes.\n10. **First call is a cold load** — a model not yet resident pays a load (and download, if the snapshot isn't cached) on first request; subsequent calls are fast until the idle sweeper unloads it.\n\nFile v1.2.1:_meta.json\n\n{\n  \"ownerId\": \"kn79dhvmpjng4rp2jjk8k0v5xx80ccbk\",\n  \"slug\": \"predictalot\",\n  \"version\": \"1.2.1\",\n  \"publishedAt\": 1790281449686\n}\n\nFile v1.2.1:references/setup.md\n\n# predictalot setup\n\nConsumer-facing reference for running / pointing at a predictalot instance. This skill talks to an instance you already run and trust; the notes below cover standing one up if you haven't.\n\n## Requirements\n\n- Docker\n- Optional: NVIDIA GPU + NVIDIA Container Toolkit for the CUDA image (CPU works for all five FMs; `chronos-2` is the fastest on CPU)\n- Disk for model snapshots under `/models` (per-slug sizes ≈ chronos-2 ~120 MB, timesfm-2.5 ~200 MB, moirai-2 ~50 MB, toto-1 ~580 MB, sundial-base-128m ~490 MB) plus trained tabular models under `/models/tabular/<id>/`\n\n## Security & safety\n\npredictalot is a plain HTTP + MCP service — anyone who can reach the port can call it. Before any non-local / shared deployment:\n\n- Set `PREDICTALOT_AUTH_TOKENS` to a strong generated secret, e.g. `$(openssl rand -hex 32)` — never leave it at a placeholder/default value.\n- Bind to loopback by default (`-p 127.0.0.1:8080:8080`); only expose beyond loopback behind a reverse proxy / VPN (see \"Public Access via Reverse Proxy\" below).\n\n## Quick Install\n\n### CPU\n\n```bash\ndocker run -d --name predictalot \\\n  -v $HOME/predictalot-models:/models \\\n  -e PREDICTALOT_AUTH_TOKENS=$(openssl rand -hex 32) \\\n  -p 127.0.0.1:8080:8080 \\\n  psyb0t/predictalot:latest\n```\n\n### CUDA\n\n```bash\ndocker run -d --name predictalot --gpus all \\\n  -v /srv/predictalot-models:/models \\\n  -e PREDICTALOT_AUTH_TOKENS=$(openssl rand -hex 32) \\\n  -e PREDICTALOT_DEVICE=cuda \\\n  -e PREDICTALOT_PRELOAD=chronos-2,toto-1,sundial-base-128m \\\n  -p 127.0.0.1:8080:8080 \\\n  psyb0t/predictalot:latest-cuda\n```\n\n`PREDICTALOT_DEVICE=auto` (the default) picks CUDA when available, else CPU.\n\n### docker-compose\n\n```yaml\nservices:\n  predictalot:\n    image: psyb0t/predictalot:latest\n    ports:\n      - \"127.0.0.1:8080:8080\"\n    environment:\n      PREDICTALOT_AUTH_TOKENS: \"${PREDICTALOT_AUTH_TOKENS:?set to a generated secret, e.g. openssl rand -hex 32}\"\n      PREDICTALOT_PRELOAD: chronos-2,toto-1\n    volumes:\n      - ./predictalot-models:/models\n    restart: unless-stopped\n```\n\nGenerate the token once and export it before `docker compose up` (e.g. `export PREDICTALOT_AUTH_TOKENS=$(openssl rand -hex 32)`) — never commit a real token or ship the example value as-is. The token MUST be changed before any non-local/shared deployment.\n\n**Verify:** `curl http://localhost:8080/healthz` returns `{\"ok\": true}` once boot is done.\n\n**Snapshots:** foundation-model weights download into `/models/<slug>/` on first use (or at boot when listed in `PREDICTALOT_PREFETCH`). Bind-mount `/models` so restarts are no-ops. Trained tabular models persist under `/models/tabular/<id>/`.\n\n## Environment Variables\n\nAll runtime config is `PREDICTALOT_*` — set via `docker run -e`, compose `environment:`, or a k8s ConfigMap.\n\n### Auth + bind\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_AUTH_TOKENS` | (empty) | Comma-separated bearer tokens. Empty = **refused at startup** unless `PREDICTALOT_ALLOW_NO_AUTH=1`. When set, `Authorization: Bearer <token>` (or `?apiToken=<token>`) required on every `/v1/*` and `/mcp` request. |\n| `PREDICTALOT_ALLOW_NO_AUTH` | `0` | Explicit opt-in to run with no tokens (open auth). Required to start with an empty token list. |\n| `PREDICTALOT_HOST` | `0.0.0.0` | uvicorn bind host. |\n| `PREDICTALOT_PORT` | `8080` | uvicorn bind port (inside the container). |\n\nControl network exposure at `docker run` time:\n- `-p 127.0.0.1:8080:8080` — loopback only on the host.\n- `-p 8080:8080` — all host interfaces.\n\n### Device + model registry\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_DEVICE` | `auto` | `auto` / `cpu` / `cuda` / `cuda:N`. |\n| `PREDICTALOT_MODEL_DIR` | `/models` | Where snapshot dirs land (and tabular models: `/models/tabular/<id>/`). **Bind-mount this** to persist. |\n| `PREDICTALOT_PREFETCH` | (empty) | Comma-separated slugs or `all` — downloaded at container start before uvicorn boots. |\n| `PREDICTALOT_PRELOAD` | (empty) | Comma-separated slugs loaded into memory at boot (skips first-call cold load). |\n\n### Lifecycle (idle unloading)\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_MODEL_IDLE_TIMEOUT` | `30m` | Idle time before a loaded FM is unloaded. Go-style durations (`30m`, `1h`, `1d2h3m`). `0` disables auto-unload. |\n| `PREDICTALOT_MODEL_IDLE_TIMEOUT_<SLUG>` | inherits global | Per-model override. Slug normalized: uppercase + `-`/`.` → `_` (e.g. `PREDICTALOT_MODEL_IDLE_TIMEOUT_MOIRAI_2`). |\n\nA background sweeper runs every 60 s and unloads models idle past their timeout.\n\n### Limits + per-model caps\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_MAX_BODY_SIZE` | `32mb` | Cap on request body — over → 413. Human-readable (`32mb`, `512k`, `1g`) or plain int bytes. |\n| `PREDICTALOT_TIMESFM_MAX_CONTEXT` | `2048` | Compile-time max context for TimesFM. Multiple of 32. |\n| `PREDICTALOT_TIMESFM_MAX_HORIZON` | `512` | Compile-time max horizon for TimesFM. Multiple of 128. |\n| `PREDICTALOT_MOIRAI_MAX_CONTEXT` | `4000` | Wrapper context length for Moirai-2 (shorter inputs zero-padded). |\n| `PREDICTALOT_MOIRAI_MAX_HORIZON` | `512` | Upper horizon for Moirai-2. Wrappers are cached per requested horizon. Multivariate requests have a 64-step native limit. |\n\n### Sundial sidecar + logging\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_SUNDIAL_SOCK` | `/tmp/predictalot/sundial.sock` | Unix socket the main service uses to reach the sundial sidecar venv. |\n| `PREDICTALOT_SUNDIAL_NUM_SAMPLES` | `64` | Monte-Carlo samples per sundial forecast (more = smoother quantiles, linearly slower). |\n| `PREDICTALOT_SUNDIAL_READY_TIMEOUT` | `60s` | How long to wait for the sundial sidecar on first request. |\n| `PREDICTALOT_LOG_LEVEL` | `INFO` | `DEBUG` / `INFO` / `WARNING` / `ERROR`. |\n\n## Ports\n\n| Port | Service |\n|---|---|\n| 8080 | HTTP API + MCP (`/mcp`) on the same port |\n\nContainer binds `0.0.0.0:8080` by default. Use `-p` at `docker run` for whatever host mapping you want.\n\n## Management\n\n```bash\ndocker logs -f predictalot            # tail logs (sundial worker stderr tagged [sundial])\ndocker stop predictalot               # stop\ndocker rm predictalot                 # remove\ndocker pull psyb0t/predictalot:latest # update\n```\n\nInspect model state without stopping anything:\n\n```bash\ncurl -s http://localhost:8080/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n```\n\nFree every resident foundation model early when the next task does not need it:\n\n```bash\ncurl -s -X POST http://localhost:8080/v1/models/unload \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n```\n\nThe endpoint returns `409` while a foundation forecast is active. A forecast body with `\"unload\": true` instead schedules that model for teardown after concurrent forecasts using the same model have finished.\n\n## OpenClaw / ClawHub Config\n\n```bash\nexport PREDICTALOT_URL=http://localhost:8080\nexport PREDICTALOT_AUTH_TOKEN=<token>   # only if the server requires it\n```\n\nOr via `~/.openclaw/openclaw.json`:\n\n```json\n{\n  \"skills\": {\n    \"entries\": {\n      \"predictalot\": {\n        \"env\": {\n          \"PREDICTALOT_URL\": \"http://localhost:8080\",\n          \"PREDICTALOT_AUTH_TOKEN\": \"<token>\"\n        }\n      }\n    }\n  }\n}\n```\n\n## Public Access via Reverse Proxy (optional)\n\nFor public exposure, terminate TLS at a reverse proxy (Caddy / Traefik / nginx) and combine it with `PREDICTALOT_AUTH_TOKENS`.\n\n```caddy\npredictalot.example.com {\n    reverse_proxy localhost:8080\n}\n```\n\nSet the auth token on the container so even a misconfigured proxy still requires `Authorization: Bearer`. Don't rely on the proxy alone. The same logic applies to Cloudflare Tunnel / Tailscale — the tunnel provides transport security, the bearer token provides app-layer auth.\n\nFile v1.2.1:skill-card.md\n\n## Description:\n\nGuides agents in using a self-hosted service for time-series forecasts, ensembles, and trained tabular predictions.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[psyb0t](https://clawhub.ai/user/psyb0t)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and analysts use this skill to request time-series forecasts with intervals or sample paths, combine models, and train tabular predictors on their own features through a trusted predictalot server.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Forecast and training data are sent to the configured server.\n\nMitigation: Use a server you trust, prefer HTTPS, and avoid sharing sensitive data with untrusted instances.\n\nRisk: An exposed service may accept requests from unauthorized users.\n\nMitigation: Bind to localhost or protect external access with TLS or VPN and a strong bearer token.\n\nRisk: A mutable Docker image tag may change between deployments.\n\nMitigation: Pin the image to a reviewed version or digest rather than using latest.\n\nRisk: Deleting a stored tabular model is irreversible.\n\nMitigation: Confirm the model identifier from the server and obtain explicit user approval before deletion.\n\n## Reference(s):\n\n- [predictalot on ClawHub](https://clawhub.ai/psyb0t/skills/predictalot)\n- [predictalot setup guide](references/setup.md)\n- [predictalot project homepage (declared by skill)](https://github.com/psyb0t/docker-predictalot)\n- [Model Context Protocol](https://modelcontextprotocol.io)\n\n## Skill Output:\n\n**Output Type(s):** [Guidance, Shell commands, Configuration, API requests]\n\n**Output Format:** [Markdown with shell commands and JSON examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Forecast responses may include quantile intervals, sample paths, or tabular predictions.]\n\n## Skill Version(s):\n\n1.2.1 (source: server-resolved release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.2.0: 5 files, 17961 bytes\n\nFiles: references/setup.md (7810b), scripts/predictalot.sh (2122b), skill-card.md (2317b), SKILL.md (34632b), _meta.json (130b)\n\nFile v1.2.0:SKILL.md\n\n---\nname: predictalot\ndescription: Self-hosted forecasting/prediction service. Foundation time-series endpoints under /v1/timeseries/<type>/{forecast,forecast/ensemble} + GET .../models — univariate, past/future/both covariates, multivariate, samples — over 5 zero-shot models (chronos-2, timesfm-2.5, moirai-2, toto-1, sundial-base-128m). Plus supervised tabular ML under /v1/tabular/ (9 backends — lightgbm, xgboost, hist-gbt, random-forest, logistic, mlp, svm-rbf, knn, naive-bayes — over direction/value/quantile modes) with train+persist, weighted ensembles, and calibrated/stacking/diversified meta-learners. Unified REST + MCP (streamable-HTTP at /mcp, one tool per (type, model) cell + per-type ensemble + listing) + optional bearer auth. Use when the user wants to forecast a numeric time series (quantile bands or raw sample paths), condition a forecast on known/future covariates, ensemble several forecasters, or train a tabular model on engineered features and predict direction/value/quantiles on the latest snapshot.\nhomepage: https://github.com/psyb0t/docker-predictalot\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🔮\", \"primaryEnv\": \"PREDICTALOT_URL\", \"requires\": { \"bins\": [\"docker\", \"curl\"] } } }\npermissions:\n  network: \"outbound HTTP(S) to the configured PREDICTALOT_URL only (forecast/train/model-management calls + MCP at /mcp)\"\n  shell: \"docker + curl invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# predictalot\n\nSelf-hosted forecasting service — one HTTP container, two model families.\n\n- **Foundation time-series (zero-shot)** — 5 forecasters (`chronos-2`, `timesfm-2.5`, `moirai-2`, `toto-1`, `sundial-base-128m`). Hand them a context window, get quantile bands or raw sample paths. No training step. Routed by forecast type under `/v1/timeseries/<type>/` — `univariate`, `covariates/past`, `covariates/future`, `covariates` (past+future), `multivariate`, `samples`. Each type has `forecast`, `forecast/ensemble`, and a `models` listing.\n- **Tabular ML (supervised)** — 9 learners (`lightgbm`, `xgboost`, `hist-gbt`, `random-forest`, `logistic`, `mlp`, `svm-rbf`, `knn`, `naive-bayes`), all supporting 3 modes (`direction` / `value` / `quantile`). Train on YOUR engineered features, persist server-side by `modelId`, forecast on the latest feature snapshot. Weighted ensembles over stored models plus 3 meta-learners (`calibrated` / `stacking` / `diversified`). Under `/v1/tabular/`.\n- **MCP** — streamable-HTTP tools at `/mcp`. One tool per (FM type, model) cell plus per-type ensemble + listing. Tabular is HTTP-only.\n- **Model lifecycle** — `POST /v1/models/unload` releases every resident foundation model. A forecast's `unload: true` waits for concurrent forecasts using that model, then tears it down. The MCP equivalent is `unload_models`.\n- **Auth** — optional bearer token (`PREDICTALOT_AUTH_TOKENS` on the server; refuses to start with no tokens unless `PREDICTALOT_ALLOW_NO_AUTH=1`).\n\nFor installation, configuration, and container setup, see [references/setup.md](references/setup.md).\n\n## Security & safety\n\n- **Network-exposed** — predictalot is a plain HTTP + MCP service; anyone who can reach the port can call it. Set `PREDICTALOT_AUTH_TOKENS` to a strong generated secret (`$(openssl rand -hex 32)`) and bind to loopback (`-p 127.0.0.1:8080:8080`) by default. Only expose beyond loopback behind a reverse proxy / VPN, and never with the default/example token.\n- **External transmission** — every forecast/train call sends your time series, engineered feature values, and/or `modelId`s to whatever `PREDICTALOT_URL` points at — that data leaves your host. Point it only at a service you run or explicitly trust; prefer HTTPS.\n- **Consumer-only** — this skill talks to an instance you already run and trusts. It never provisions, starts, or hardens the server; that's the operator's job (see setup.md).\n- **Destructive delete requires confirmation** — `DELETE /v1/tabular/models/{modelId}` permanently removes a trained model and cannot be undone. Only call it against a `modelId` you obtained from a prior `GET /v1/tabular/models` (or a train response) in this session, and get explicit user confirmation before issuing the delete.\n\n## When To Use\n\n- Forecast a numeric time series N steps ahead and get calibrated quantile bands (`0.1`/`0.5`/`0.9`, etc.) — zero-shot, no training.\n- Get raw Monte-Carlo sample paths (via the `samples` type) to compute custom risk metrics / joint distributions across horizon steps.\n- Condition a forecast on covariates: known-history (`covariates/past`), forward-known drivers like a planned promotion or price schedule (`covariates/future`), or both at once (`covariates`).\n- Forecast several correlated channels jointly (`multivariate`).\n- Combine multiple forecasters into a weighted ensemble and inspect each member's individual forecast + applied weight.\n- Train a supervised model on engineered features and predict next-bar direction (`P(up)`), a point value, or quantiles on the latest snapshot.\n- Combine trained tabular models via ensemble or a `calibrated` / `stacking` / `diversified` meta-learner.\n\n## When NOT To Use\n\n- Real-time / streaming forecasts — every endpoint is request/response only.\n- Automatic feature engineering on the tabular side — you supply the features; predictalot does not derive indicators or lags for you.\n- `timesfm-2.5` for accuracy — it is the weakest of the five on every benchmarked dataset. Skip it or set `weights={\"timesfm-2.5\": 0}` in ensembles.\n- Commercial use of `moirai-2` — it ships under CC-BY-NC-4.0 (non-commercial). The other four FMs are Apache 2.0.\n- Covariate/multivariate/samples types on models that don't support them — membership is fixed per type (see below). A non-member `model` → 400.\n- Provisioning or hardening the server from here — this skill is a **consumer**. It talks to an instance the user already runs and trusts; it never launches, escalates, or reconfigures the container.\n\n## Setup\n\nThe container should already be running. Point at it:\n\n```bash\nexport PREDICTALOT_URL=http://localhost:8080\n```\n\nIf the server has `PREDICTALOT_AUTH_TOKENS` set, export a token too:\n\n```bash\nexport PREDICTALOT_AUTH_TOKEN=<your-token>\n# every /v1/* and /mcp request below needs: -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\"\n```\n\n**Verify:** `curl $PREDICTALOT_URL/healthz` returns `{\"ok\": true}`. (`/healthz` is unauthenticated.)\n\nFor install / configuration / env vars / CPU vs CUDA images, see [references/setup.md](references/setup.md).\n\n## Models & Types\n\nFoundation models, and which forecast types each supports (a `model` outside a type's member set → 400):\n\n| Model | Univariate | Multivariate | Cov: past | Cov: future | Cov: both | Samples | License | Recommended for |\n|---|:-:|:-:|:-:|:-:|:-:|:-:|---|---|\n| `chronos-2` | ✓ | ✓ | ✓ | ✓ | ✓ | — | Apache 2.0 | Default all-rounder; fastest on CPU; only model with future/both covariates. |\n| `timesfm-2.5` | ✓ | — | — | — | — | — | Apache 2.0 | Weakest on benchmarks — skip or zero-weight it. Univariate-only; compile-time horizon cap. |\n| `moirai-2` | ✓ | ✓ | ✓ | — | — | — | CC-BY-NC-4.0 | Clean cyclic/seasonal series; correlated channels. Non-commercial license. |\n| `toto-1` | ✓ | ✓ | — | — | — | ✓ | Apache 2.0 | Noisy / observability / financial series; exposes raw sample paths. |\n| `sundial-base-128m` | ✓ | — | — | — | — | ✓ | Apache 2.0 | Drifting / trending series; generative sample paths. Runs in a sidecar venv. |\n\nType → URL prefix:\n\n| Type | URL prefix | Members |\n|---|---|---|\n| univariate | `/v1/timeseries/univariate` | chronos-2, timesfm-2.5, moirai-2, toto-1, sundial-base-128m |\n| multivariate | `/v1/timeseries/multivariate` | chronos-2, moirai-2, toto-1 |\n| covariates (past) | `/v1/timeseries/covariates/past` | chronos-2, moirai-2 |\n| covariates (future) | `/v1/timeseries/covariates/future` | chronos-2 |\n| covariates (past+future) | `/v1/timeseries/covariates` | chronos-2 |\n| samples | `/v1/timeseries/samples` | toto-1, sundial-base-128m |\n\nDiscover live per-type membership + runtime state with `GET /v1/timeseries/<type>/models`. Discover tabular backends with `GET /v1/tabular/backends`.\n\nTabular backends (all support `direction` / `value` / `quantile`):\n\n| Slug | Display name | Category |\n|---|---|---|\n| `lightgbm` | LightGBM | boosting |\n| `xgboost` | XGBoost | boosting |\n| `hist-gbt` | HistGradientBoosting (sklearn) | boosting |\n| `random-forest` | Random Forest | bagging |\n| `logistic` | Logistic / Ridge / QuantileRegressor (linear baselines) | linear |\n| `mlp` | Multi-Layer Perceptron (sklearn) | neural |\n| `svm-rbf` | SVM with RBF kernel | kernel |\n| `knn` | k-Nearest Neighbors | distance |\n| `naive-bayes` | Gaussian Naive Bayes / BayesianRidge | independence |\n\n## Quick Start\n\n> Every forecast/train call below sends your time series, engineered features, and/or `modelId`s over the network to `$PREDICTALOT_URL`. Only point this at a trusted, self-hosted instance you control, prefer HTTPS, treat proprietary datasets as sensitive, and never echo `PREDICTALOT_AUTH_TOKEN` in output.\n\n```bash\n# Health (unauthenticated).\ncurl -s $PREDICTALOT_URL/healthz | jq\n\n# List univariate models + their runtime state.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n\n# Zero-shot univariate forecast: 5 steps ahead of one series, chronos-2.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n        \"model\": \"chronos-2\",\n        \"context\": [[10,11,12,13,14,15,16,17,18,19,20]],\n        \"config\": { \"horizon\": 5, \"quantileLevels\": [0.1, 0.5, 0.9] }\n      }' | jq\n```\n\nWire format is **camelCase** (`quantileLevels`, `contextLength`, `pastCovariates`, `futureCovariates`, `numSamples`, `memberOverrides`, `modelId`). All `/v1/*` routes require the bearer header when the server has tokens configured; drop the header for an open-auth deployment.\n\nShapes recur across the timeseries API: `context` is `[series][time]` (a batch of independent series), `horizon` > 0, `quantileLevels` is a subset of `{0.1, 0.2, …, 0.9}` (default `[0.1, 0.5, 0.9]`), `contextLength` caps history fed to the model (omit → per-model default), `unload: true` tears the model down after the response. `median` and `quantiles[\"0.5\"]` are the same for most models but can differ for chronos-2 (its `median` is the distribution mean).\n\nRelease every resident foundation model when the next task will not need one:\n\n```bash\ncurl -s -X POST $PREDICTALOT_URL/v1/models/unload \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n```\n\nThe request returns `409` while a foundation forecast is active. Do not retry it blindly. Wait for the forecast result, then retry when memory needs to be freed.\n\n---\n\n## API — `POST /v1/timeseries/univariate/forecast`\n\nSingle-model quantile forecast over a batch of independent series. `context` (your time series data) transmits off-box to `$PREDICTALOT_URL` — same data-transfer note as Quick Start applies to every timeseries endpoint below.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `model` | yes | — | One of the univariate members. Not a member → 400; unknown slug → 404. |\n| `context` | yes | — | `[series][time]` — one inner list of floats per series. |\n| `config.horizon` | yes | — | Steps ahead. Must be `> 0` (else 422). |\n| `config.quantileLevels` | no | `[0.1, 0.5, 0.9]` | Subset of `{0.1..0.9}` step 0.1. Out-of-range → 400. |\n| `config.contextLength` | no | per-model | Max history points fed to the model. `> 0`. |\n| `config.extra` | no | — | Per-backend escape-hatch dict, camelCased. Unknown keys ignored. E.g. chronos-2: `batchSize`, `crossLearning`, `limitPredictionLength`; toto-1: `numSamples`, `samplesPerBatch`, `useKvCache`; timesfm-2.5: `normalizeInputs`, `fixQuantileCrossing`, … |\n| `unload` | no | `false` | Unload the model after responding. |\n\n### Response\n\n```json\n{\n  \"model\": \"chronos-2\",\n  \"horizon\": 5,\n  \"quantileLevels\": [0.1, 0.5, 0.9],\n  \"median\": [[20.9, 21.8, 22.7, 23.6, 24.5]],\n  \"quantiles\": {\n    \"0.1\": [[20.1, 20.8, 21.5, 22.1, 22.7]],\n    \"0.5\": [[20.9, 21.8, 22.7, 23.6, 24.5]],\n    \"0.9\": [[21.7, 22.9, 24.0, 25.1, 26.3]]\n  }\n}\n```\n\n`median` is `[series][time]`; `quantiles` maps each level string → `[series][time]`.\n\n### Error Contract\n\n| Status | Shape | When |\n|---|---|---|\n| 200 | forecast JSON | success |\n| 400 | `{\"detail\": \"...\"}` | empty context/series, model not a member of the type, quantile level outside `{0.1..0.9}`, horizon over a model's compile-time cap (timesfm-2.5 / moirai-2) |\n| 401 | `{\"detail\": \"...\"}` | tokens configured, missing/wrong bearer |\n| 404 | `{\"detail\": \"...\"}` | unknown `model` slug |\n| 413 | `{\"detail\": \"...\"}` | body over `PREDICTALOT_MAX_BODY_SIZE` |\n| 422 | `{\"detail\": [...]}` | Pydantic validation (missing/typed fields, `horizon` not `> 0`) |\n| 503 | `{\"detail\": \"...\"}` | snapshot download failed, inference threw, or sundial sidecar unreachable |\n\n---\n\n## API — `POST /v1/timeseries/univariate/forecast/ensemble`\n\nWeighted mean across every univariate member. No `model` field — membership is the whole type.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `context` | yes | — | `[series][time]`. |\n| `config` | yes | — | Same `ForecastConfig` as single forecast (`horizon`, `quantileLevels`, `contextLength`, `extra`). |\n| `weights` | no | uniform | `{slug: float ≥ 0}`. Missing slugs default to 1.0; weight `0` disables a member; unknown slug → 400. |\n| `memberOverrides` | no | — | `{slug: partial-config}` — per-member overrides of the global `config` (e.g. give one member a different `contextLength` or `extra`). |\n| `unload` | no | `false` | Unload members after responding. |\n\n### Response\n\nAggregated `median` + `quantiles` (same shapes as single forecast) with `model: \"ensemble\"`, plus:\n\n```json\n{\n  \"model\": \"ensemble\",\n  \"horizon\": 5,\n  \"quantileLevels\": [0.1, 0.5, 0.9],\n  \"median\": [[...]],\n  \"quantiles\": { \"0.1\": [[...]], \"0.5\": [[...]], \"0.9\": [[...]] },\n  \"ensembleMembers\": [\"chronos-2\", \"moirai-2\", \"toto-1\"],\n  \"weights\": { \"chronos-2\": 0.5, \"moirai-2\": 0.25, \"toto-1\": 0.25 },\n  \"individual\": {\n    \"chronos-2\": { \"model\": \"chronos-2\", \"horizon\": 5, \"quantileLevels\": [...],\n                   \"median\": [[...]], \"quantiles\": {...}, \"weight\": 0.5 }\n  }\n}\n```\n\n`individual[slug]` is that member's full single-forecast response + its applied `weight`.\n\n### Error Contract\n\nSame as single univariate, minus 404 (no `model` field) — an unknown `weights` slug is a 400, not 404.\n\n---\n\n## API — `POST /v1/timeseries/covariates/past/forecast`\n\nForecast each target series conditioned on covariates known only up to `t`. Members: `chronos-2`, `moirai-2`.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `model` | yes | — | A past-covariate member. |\n| `context` | yes | — | `[series][time]` — the univariate targets. |\n| `pastCovariates` | yes | — | `list[dict[name, float[]]]` — one mapping per series; every value array is the **same length as that series' context**. All series share the same covariate names. |\n| `config` | yes | — | `ForecastConfig` (`horizon`, `quantileLevels`, `contextLength`, `extra`). |\n| `unload` | no | `false` | |\n\n### Response\n\nIdentical shape to univariate single forecast (`model`, `horizon`, `quantileLevels`, `median`, `quantiles`).\n\n### Error Contract\n\nSame table as univariate, plus 400 when a `pastCovariates` value length doesn't match the matching `context` series.\n\n---\n\n## API — `POST /v1/timeseries/covariates/past/forecast/ensemble`\n\nWeighted ensemble over the past-covariate members. Fields = past forecast fields plus `weights` + `memberOverrides` (as in the univariate ensemble). Response = univariate ensemble shape.\n\n---\n\n## API — `POST /v1/timeseries/covariates/future/forecast`\n\nForecast conditioned on covariates known only over the **future window** (length == `horizon`) — e.g. a planned promotion, a published price schedule, a weather forecast. Member: `chronos-2` only.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `model` | yes | — | `chronos-2`. |\n| `context` | yes | — | `[series][time]` targets. |\n| `futureCovariates` | yes | — | `list[dict[name, float[]]]` — one mapping per series; **each value array of length `horizon`**. All series share names. |\n| `config` | yes | — | `ForecastConfig`. |\n| `unload` | no | `false` | |\n\n### Response\n\nUnivariate single-forecast shape.\n\n### Error Contract\n\nUnivariate table + 400 when a `futureCovariates` value length ≠ `config.horizon`.\n\n---\n\n## API — `POST /v1/timeseries/covariates/future/forecast/ensemble`\n\nEnsemble over future-covariate members (currently just `chronos-2`, so only useful once more back it). Fields = future forecast + `weights` + `memberOverrides`. Response = univariate ensemble shape.\n\n---\n\n## API — `POST /v1/timeseries/covariates/forecast` (past + future)\n\nCombined past **and** future covariates in one call. Member: `chronos-2` only.\n\n### Request Fields\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `model` | yes | — | `chronos-2`. |\n| `context` | yes | — | `[series][time]` targets. |\n| `pastCovariates` | yes | — | Per series, same length as context. |\n| `futureCovariates` | yes | — | Per series, length `horizon`. **Every future-covariate name MUST also appear in `pastCovariates`** for that series (chronos-2 constraint) — else 400. |\n| `config` | yes | — | `ForecastConfig`. |\n| `unload` | no | `false` | |\n\n### Response\n\nUnivariate single-forecast shape.\n\n### Error Contract\n\nUnivariate table + 400 for length mismatches or a future-covariate name missing from `pastCovariates`.\n\n---\n\n## API — `POST /v1/timeseries/covariates/forecast/ensemble`\n\nEnsemble over past+future members. Fields = past+future forecast + `weights` + `memberOverrides`. Response = univariate ensemble shape.\n\n---\n\n## API — `GET /v1/timeseries/<type>/models`\n\nPer-type model listing + runtime state. `<type>` ∈ `univariate`, `multivariate`, `covariates/past`, `covariates/future`, `covariates`, `samples`.\n\n### Response\n\n```json\n{\n  \"type\": \"univariate\",\n  \"models\": [\n    { \"slug\": \"chronos-2\", \"loaded\": true, \"lastUsedSecsAgo\": 12.4, \"idleTimeoutSecs\": 1800.0 },\n    { \"slug\": \"timesfm-2.5\", \"loaded\": false, \"lastUsedSecsAgo\": null, \"idleTimeoutSecs\": 1800.0 }\n  ]\n}\n```\n\n`lastUsedSecsAgo` is `null` when the model has never loaded. `idleTimeoutSecs` reflects `PREDICTALOT_MODEL_IDLE_TIMEOUT` (or a per-slug override); `0` means never auto-unloaded.\n\n### Error Contract\n\n200 on success; 401 when tokens are configured and the bearer is missing/wrong.\n\n---\n\n## API — `GET /v1/tabular/backends`\n\nList the supervised tabular backends and the modes each supports. Use this to discover the live set before a train call.\n\n### Response\n\n```json\n{\n  \"backends\": [\n    { \"slug\": \"lightgbm\", \"displayName\": \"LightGBM\", \"category\": \"boosting\",\n      \"supportedModes\": [\"direction\", \"quantile\", \"value\"] },\n    { \"slug\": \"xgboost\", \"displayName\": \"XGBoost\", \"category\": \"boosting\",\n      \"supportedModes\": [\"direction\", \"quantile\", \"value\"] }\n  ]\n}\n```\n\n### Error Contract\n\n200 on success; 401 when tokens are configured and the bearer is missing/wrong.\n\n---\n\n## Tabular API (train → forecast)\n\nThe tabular side trains on YOUR engineered features and persists a model server-side by `modelId`. `target` / `features` are generic float lists — no OHLC / indicator assumptions. `features` is per series: `featureName → time-aligned float list`. Same data-transfer note as Quick Start applies: `target`/`features`/`modelId` all transmit to `$PREDICTALOT_URL`.\n\n### `POST /v1/tabular/train`\n\n| Field | Required | Default | Notes |\n|---|---|---|---|\n| `modelId` | yes | — | Caller-chosen id, used for storage + later forecast lookup. |\n| `backend` | yes | — | A slug from `GET /v1/tabular/backends`. Unknown → 404. |\n| `target` | yes | — | `[series][time]` — the scalar series to predict. Empty → 400. |\n| `features` | yes | — | `list[dict[name, float[]]]` — same length + names per series as `target`. Count must match `target` (else 400); differing key sets across series → 400. |\n| `config.mode` | yes | — | `direction` (sign of `target[t+h] - target[t]`) / `value` (regress `target[t+h]`) / `quantile`. Mode not supported by the backend → 400. |\n| `config.horizon` | yes | — | Bars ahead (`t+h`). `> 0`. |\n| `config.quantileLevels` | when `quantile` | — | Required for `mode=\"quantile\"`; subset of `{0.1..0.9}`. |\n| `config.*` (tier-2) | no | — | `nEstimators`, `maxDepth`, `learningRate`, `numLeaves`, `minSamples`, `randomState`, `categoricalFeatures`, `monotonicConstraints`, `classWeight`, `sampleWeight`, `earlyStoppingRounds`, `validationFraction`. Backends ignore knobs they don't use. |\n| `config.extra` | no | — | Per-backend hyperparams (e.g. svm-rbf reads `C`/`gamma`; mlp reads `hiddenLayerSizes`/`activation`; knn reads `nNeighbors`/`weights`/`metric`). |\n| `overwrite` | no | `false` | Reuse an existing `modelId` → 409 unless `true`. |\n\n**Response** (`TrainResponse`): `modelId`, `backend`, `mode`, `horizon`, `nTrainingRows`, `nFeatures`, `featureNames`, `featureImportance` (`{name: float}`), `trainSecs`.\n\n### `POST /v1/tabular/forecast`\n\nRuns a stored model on the **latest** feature snapshot — the LAST row per series is the anchor; earlier rows are ignored.\n\n| Field | Required | Notes |\n|---|---|---|\n| `modelId` | yes | Missing → 404. If its backend is no longer registered → 410. |\n| `features` | yes | `list[dict[name, float[]]]`; names must match training. Missing a trained name → 400. |\n\n**Response** (`ForecastResponse`) is mode-dependent:\n- `direction`: `probUp: float[]` (per series) + `confidence: float[]` (`|probUp-0.5|*2`).\n- `value`: `predicted: float[]`.\n- `quantile`: `median: float[series][1]` + `quantiles: {level: float[series][1]}`.\n\n### `POST /v1/tabular/forecast/ensemble`\n\nWeighted combination of several stored models on the same features; all members must share `mode`, `horizon`, and `featureNames` (mismatch → 400).\n\n| Field | Required | Notes |\n|---|---|---|\n| `modelIds` | yes | Stored ids to combine (≥ 1). Any missing → 404. |\n| `weights` | no | `{modelId: float ≥ 0}`. None → uniform; `0` removes a member; unknown id or negative → 400. |\n| `features` | yes | As in forecast; last row per series is the anchor. |\n\n**Response** (`EnsembleForecastResponse`): `mode`, `horizon`, `ensembleMembers`, normalized `weights`, `individual` (`{modelId: member response}`), plus the combined mode-specific fields (`probUp`+`confidence` / `predicted` / `median`+`quantiles`).\n\n### `GET /v1/tabular/models` and `DELETE /v1/tabular/models/{modelId}` (destructive)\n\n`GET` lists stored models: `{\"models\": [{ modelId, backend, mode, horizon, nFeatures, featureNames, nTrainingRows, trainedAtUnix }]}`. `DELETE` removes one (`{\"modelId\": \"...\", \"removed\": true}`; 404 if absent) — **irreversible**. Only delete a `modelId` returned by a prior `GET /v1/tabular/models` call (or a train response), and get explicit user confirmation first.\n\n### Meta-learners\n\nComposite endpoints that hold out / cross-validate internally so you don't have to orchestrate it client-side. Each persists one blob under a `meta:<kind>` backend tag and forecasts via `POST /v1/tabular/forecast/<kind>` with `{modelId, features}`.\n\n- `POST /v1/tabular/train/calibrated` — base learner + post-hoc probability calibrator. **`direction` only.** Fields: `modelId`, `baseBackend`, `target`, `features`, `config` (mode must be `direction`), `calibrationMethod` (`sigmoid`|`isotonic`, default `sigmoid`), `calibrationFraction` (default `0.2`, in `(0,1)`), `overwrite`. Too-small split → 400.\n- `POST /v1/tabular/train/stacking` — K base learners + a meta-learner on K-fold OOF predictions. **`direction` only (v1).** Fields: `modelId`, `members` (≥ 2 `{backend, config}`, all `direction`, horizon == top-level), `metaBackend` (default `logistic`), `target`, `features`, `horizon`, `nFolds` (2–10, default 5), `overwrite`. Needs `≥ nFolds*5` rows. Response includes `oofScore` (AUC).\n- `POST /v1/tabular/train/diversified` — trains K candidates, selects a low-correlation subset by OOF Pearson correlation, combines equal-weight. Supports all 3 modes. Fields: `modelId`, `candidates` (≥ 2, mode+horizon must match top-level), `target`, `features`, `horizon`, `mode`, `quantileLevels` (required if `quantile`), `nFolds` (2–10, default 3), `maxPairwiseCorr` (0–1, default 0.85), `minMembers`, `maxMembers`, `overwrite`. Response includes `candidateCorr`.\n\nMeta-train responses (`MetaTrainResponse`): `modelId`, `kind`, `mode`, `horizon`, `membersUsed`, `nTrainingRows`, `nFeatures`, `featureNames`, `trainSecs`, plus `oofScore` (stacking) / `candidateCorr` (diversified). Meta-forecast responses (`MetaForecastResponse`): `modelId`, `kind`, `mode`, `horizon`, `members` (per-member breakdown), `selectedMembers` (diversified), and the mode-specific combined fields.\n\n### Tabular Error Contract\n\n| Status | When |\n|---|---|\n| 200 | success |\n| 400 | empty `target`; `target`/`features` count mismatch; differing feature key sets; mode unsupported by backend; `quantile` without `quantileLevels`; forecast features missing a trained name; ensemble member mode/horizon/features mismatch; unknown/negative ensemble weight; meta constraint violated (calibrated non-direction, stacking non-direction, diversified quantile w/o levels, split too small) |\n| 401 | tokens configured, missing/wrong bearer |\n| 404 | unknown tabular `backend` on train; `modelId` not found on forecast/meta |\n| 409 | train with `overwrite: false` against an existing `modelId` |\n| 410 | forecast against a `modelId` whose backend is no longer registered |\n| 413 | body over `PREDICTALOT_MAX_BODY_SIZE` |\n| 422 | Pydantic validation |\n| 503 | training/inference threw |\n\n---\n\n## MCP Endpoint (`/mcp`)\n\npredictalot mounts a [Model Context Protocol](https://modelcontextprotocol.io) server over Streamable HTTP at `/mcp`, in the same process, behind the same bearer auth. The tool surface mirrors the **foundation-model** routes only — tabular is HTTP-only.\n\nFor each forecast type there are three classes of tool:\n- `forecast_<type>_<model>` — single-model forecast for one (type, model) cell. Types are underscore-normalized: `univariate`, `multivariate`, `covariates_past`, `covariates_future`, `covariates_both`, `samples`; model slugs are underscore-normalized too (`chronos-2` → `chronos_2`). E.g. `forecast_univariate_chronos_2`, `forecast_covariates_future_chronos_2`.\n- `forecast_<type>_ensemble` — weighted ensemble over every model supporting that type.\n- `list_<type>_models` — which models implement the type + runtime state (`{type, models: [{slug, loaded, lastUsedSecsAgo, idleTimeoutSecs}]}`).\n\nTool args mirror the HTTP bodies but flattened (no nested `config`): `context`, `horizon`, `quantile_levels?`, `context_length?`, `unload?`; covariate tools add `past_covariates` / `future_covariates`; ensemble tools add `weights?`; samples tools use `num_samples?` instead of quantile levels and return `samples` `[series][sample][time]` + `median`.\n\nWire it into Claude Code (auth optional — the token may also be passed as `?apiToken=<token>`):\n\n```bash\nclaude mcp add --transport http predictalot $PREDICTALOT_URL/mcp \\\n  --header \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\"\n```\n\n### Raw JSON-RPC\n\nThe transport requires `Accept: application/json, text/event-stream`.\n\n```bash\n# tools/list — enumerate every (type, model) tool + ensembles + listings.\ncurl -s $PREDICTALOT_URL/mcp/ \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/list\"}'\n\n# tools/call — univariate chronos-2 forecast.\ncurl -s $PREDICTALOT_URL/mcp/ \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\n    \"jsonrpc\": \"2.0\", \"id\": 2, \"method\": \"tools/call\",\n    \"params\": {\n      \"name\": \"forecast_univariate_chronos_2\",\n      \"arguments\": {\n        \"context\": [[10,11,12,13,14,15,16,17,18,19,20]],\n        \"horizon\": 5,\n        \"quantile_levels\": [0.1, 0.5, 0.9]\n      }\n    }\n  }'\n```\n\nEach tool returns a JSON-encoded string. User-input errors come back as `{\"error\": \"...\", \"context\": \"<type>/<model>\"}`; internal errors are redacted to `{\"error\": \"internal error; see server logs\", \"context\": \"...\"}`.\n\n## Bearer-Token Auth\n\nIf `PREDICTALOT_AUTH_TOKENS` (comma-separated list) is set on the server, every `/v1/*` and `/mcp` request needs `Authorization: Bearer <one-of-the-tokens>`; `/healthz` is always open. Missing/wrong → 401. Tokens are compared constant-time (`hmac.compare_digest`).\n\n```bash\nexport PREDICTALOT_AUTH_TOKEN=<your-token>\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n```\n\nThe server **refuses to start** with an empty token list unless it was launched with `PREDICTALOT_ALLOW_NO_AUTH=1` — in that open-auth mode there is no 401 and no header is needed. For untrusted networks, combine the token with a reverse proxy doing TLS + rate limiting.\n\n## Typical Workflows\n\n### Pick a model → forecast → read the intervals\n\n```bash\n# 1. See which univariate models are available + resident.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq -r '.models[].slug'\n\n# 2. Forecast with chronos-2 (default all-rounder), 12 steps, three bands.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\":\"chronos-2\",\"context\":[[100,102,101,105,110,108,112,115,120,118,125,130]],\n       \"config\":{\"horizon\":12,\"quantileLevels\":[0.1,0.5,0.9]}}' | jq\n\n# 3. Read the interval: quantiles[\"0.5\"] is the central path,\n#    [\"0.1\"]/[\"0.9\"] the 80% band — all shaped [series][time].\n```\n\n### Ensemble, zero-weighting the weak model\n\n```bash\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/forecast/ensemble \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"context\":[[100,102,101,105,110,108,112,115,120,118,125,130]],\n       \"config\":{\"horizon\":12},\n       \"weights\":{\"timesfm-2.5\":0,\"chronos-2\":2,\"toto-1\":1}}' | jq \\\n  '{members: .ensembleMembers, weights, median}'\n```\n\n### Future-covariate forecast (chronos-2)\n\n```bash\n# Target has 8 steps of history; horizon 4 → futureCovariates arrays length 4.\ncurl -s $PREDICTALOT_URL/v1/timeseries/covariates/future/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\":\"chronos-2\",\n       \"context\":[[20,22,21,25,24,27,29,31]],\n       \"futureCovariates\":[{\"promo\":[0,1,1,0]}],\n       \"config\":{\"horizon\":4}}' | jq\n```\n\n### Raw sample paths for custom risk metrics\n\n```bash\ncurl -s $PREDICTALOT_URL/v1/timeseries/samples/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"model\":\"toto-1\",\n       \"context\":[[100,102,101,105,110,108,112,115,120,118,125,130]],\n       \"config\":{\"horizon\":10,\"numSamples\":256}}' | jq '{numSamples, shape: (.samples[0]|length)}'\n# samples is [series][sample][time]; compute your own VaR / quantiles off the draws.\n```\n\n### Train a tabular direction model, then forecast the latest snapshot\n\n```bash\n# 1. Train on engineered features (you supply them).\ncurl -s $PREDICTALOT_URL/v1/tabular/train \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"modelId\":\"trend-3\",\"backend\":\"lightgbm\",\n       \"target\":[[100,101,99,102,105,103,107,110,108,112]],\n       \"features\":[{\"rsi\":[55,58,52,60,63,59,65,70,66,72],\n                    \"mom\":[0.2,0.3,-0.1,0.4,0.5,0.2,0.6,0.7,0.4,0.8]}],\n       \"config\":{\"mode\":\"direction\",\"horizon\":3,\"nEstimators\":400}}' | jq\n\n# 2. Forecast — LAST feature row is the anchor.\ncurl -s $PREDICTALOT_URL/v1/tabular/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"modelId\":\"trend-3\",\"features\":[{\"rsi\":[72],\"mom\":[0.8]}]}' | jq\n# → {\"modelId\":\"trend-3\",\"backend\":\"lightgbm\",\"mode\":\"direction\",\"horizon\":3,\n#    \"probUp\":[0.63],\"confidence\":[0.26]}\n```\n\nFor a driver that runs a univariate forecast and pretty-prints the interval, see [`scripts/predictalot.sh`](scripts/predictalot.sh):\n\n```bash\nPREDICTALOT_URL=http://localhost:8080 \\\nPREDICTALOT_AUTH_TOKEN=<token> \\\n  bash scripts/predictalot.sh chronos-2 5 10 11 12 13 14 15 16 17 18 19 20\n```\n\n## Tips\n\n1. **Default to `chronos-2`** — fastest on CPU, widest type coverage (only model with future/both covariates), solid general-purpose accuracy.\n2. **Skip or zero-weight `timesfm-2.5`** — weakest on every benchmark. In ensembles pass `weights={\"timesfm-2.5\":0}`.\n3. **`moirai-2` is CC-BY-NC-4.0** — non-commercial. Fine for research/eval, not for a commercial product.\n4. **Use `samples` for risk work** — `toto-1` / `sundial-base-128m` return raw `[series][sample][time]` paths; compute your own VaR / joint metrics instead of trusting fixed quantile cuts.\n5. **`median` ≠ `quantiles[\"0.5\"]` for chronos-2** — its `median` is the distribution mean; the others agree.\n6. **Covariate length rules** — past covariates match the context length; future covariates match `horizon`; in the past+future type every future-cov name must also be a past-cov name.\n7. **Tabular features are yours** — the API does no feature engineering. The LAST row per series is the forecast anchor.\n8. **`overwrite: false` is the default** on every train endpoint — re-training a known `modelId` is a 409 until you pass `overwrite: true`.\n9. **Discover, don't assume** — `GET /v1/timeseries/<type>/models` and `GET /v1/tabular/backends` are the live source of truth for membership and modes.\n10. **First call is a cold load** — a model not yet resident pays a load (and download, if the snapshot isn't cached) on first request; subsequent calls are fast until the idle sweeper unloads it.\n\nFile v1.2.0:_meta.json\n\n{\n  \"ownerId\": \"kn79dhvmpjng4rp2jjk8k0v5xx80ccbk\",\n  \"slug\": \"predictalot\",\n  \"version\": \"1.2.0\",\n  \"publishedAt\": 1790274286088\n}\n\nFile v1.2.0:references/setup.md\n\n# predictalot setup\n\nConsumer-facing reference for running / pointing at a predictalot instance. This skill talks to an instance you already run and trust; the notes below cover standing one up if you haven't.\n\n## Requirements\n\n- Docker\n- Optional: NVIDIA GPU + NVIDIA Container Toolkit for the CUDA image (CPU works for all five FMs; `chronos-2` is the fastest on CPU)\n- Disk for model snapshots under `/models` (per-slug sizes ≈ chronos-2 ~120 MB, timesfm-2.5 ~200 MB, moirai-2 ~50 MB, toto-1 ~580 MB, sundial-base-128m ~490 MB) plus trained tabular models under `/models/tabular/<id>/`\n\n## Security & safety\n\npredictalot is a plain HTTP + MCP service — anyone who can reach the port can call it. Before any non-local / shared deployment:\n\n- Set `PREDICTALOT_AUTH_TOKENS` to a strong generated secret, e.g. `$(openssl rand -hex 32)` — never leave it at a placeholder/default value.\n- Bind to loopback by default (`-p 127.0.0.1:8080:8080`); only expose beyond loopback behind a reverse proxy / VPN (see \"Public Access via Reverse Proxy\" below).\n\n## Quick Install\n\n### CPU\n\n```bash\ndocker run -d --name predictalot \\\n  -v $HOME/predictalot-models:/models \\\n  -e PREDICTALOT_AUTH_TOKENS=$(openssl rand -hex 32) \\\n  -p 127.0.0.1:8080:8080 \\\n  psyb0t/predictalot:latest\n```\n\n### CUDA\n\n```bash\ndocker run -d --name predictalot --gpus all \\\n  -v /srv/predictalot-models:/models \\\n  -e PREDICTALOT_AUTH_TOKENS=$(openssl rand -hex 32) \\\n  -e PREDICTALOT_DEVICE=cuda \\\n  -e PREDICTALOT_PRELOAD=chronos-2,toto-1,sundial-base-128m \\\n  -p 127.0.0.1:8080:8080 \\\n  psyb0t/predictalot:latest-cuda\n```\n\n`PREDICTALOT_DEVICE=auto` (the default) picks CUDA when available, else CPU.\n\n### docker-compose\n\n```yaml\nservices:\n  predictalot:\n    image: psyb0t/predictalot:latest\n    ports:\n      - \"127.0.0.1:8080:8080\"\n    environment:\n      PREDICTALOT_AUTH_TOKENS: \"${PREDICTALOT_AUTH_TOKENS:?set to a generated secret, e.g. openssl rand -hex 32}\"\n      PREDICTALOT_PRELOAD: chronos-2,toto-1\n    volumes:\n      - ./predictalot-models:/models\n    restart: unless-stopped\n```\n\nGenerate the token once and export it before `docker compose up` (e.g. `export PREDICTALOT_AUTH_TOKENS=$(openssl rand -hex 32)`) — never commit a real token or ship the example value as-is. The token MUST be changed before any non-local/shared deployment.\n\n**Verify:** `curl http://localhost:8080/healthz` returns `{\"ok\": true}` once boot is done.\n\n**Snapshots:** foundation-model weights download into `/models/<slug>/` on first use (or at boot when listed in `PREDICTALOT_PREFETCH`). Bind-mount `/models` so restarts are no-ops. Trained tabular models persist under `/models/tabular/<id>/`.\n\n## Environment Variables\n\nAll runtime config is `PREDICTALOT_*` — set via `docker run -e`, compose `environment:`, or a k8s ConfigMap.\n\n### Auth + bind\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_AUTH_TOKENS` | (empty) | Comma-separated bearer tokens. Empty = **refused at startup** unless `PREDICTALOT_ALLOW_NO_AUTH=1`. When set, `Authorization: Bearer <token>` (or `?apiToken=<token>`) required on every `/v1/*` and `/mcp` request. |\n| `PREDICTALOT_ALLOW_NO_AUTH` | `0` | Explicit opt-in to run with no tokens (open auth). Required to start with an empty token list. |\n| `PREDICTALOT_HOST` | `0.0.0.0` | uvicorn bind host. |\n| `PREDICTALOT_PORT` | `8080` | uvicorn bind port (inside the container). |\n\nControl network exposure at `docker run` time:\n- `-p 127.0.0.1:8080:8080` — loopback only on the host.\n- `-p 8080:8080` — all host interfaces.\n\n### Device + model registry\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_DEVICE` | `auto` | `auto` / `cpu` / `cuda` / `cuda:N`. |\n| `PREDICTALOT_MODEL_DIR` | `/models` | Where snapshot dirs land (and tabular models: `/models/tabular/<id>/`). **Bind-mount this** to persist. |\n| `PREDICTALOT_PREFETCH` | (empty) | Comma-separated slugs or `all` — downloaded at container start before uvicorn boots. |\n| `PREDICTALOT_PRELOAD` | (empty) | Comma-separated slugs loaded into memory at boot (skips first-call cold load). |\n\n### Lifecycle (idle unloading)\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_MODEL_IDLE_TIMEOUT` | `30m` | Idle time before a loaded FM is unloaded. Go-style durations (`30m`, `1h`, `1d2h3m`). `0` disables auto-unload. |\n| `PREDICTALOT_MODEL_IDLE_TIMEOUT_<SLUG>` | inherits global | Per-model override. Slug normalized: uppercase + `-`/`.` → `_` (e.g. `PREDICTALOT_MODEL_IDLE_TIMEOUT_MOIRAI_2`). |\n\nA background sweeper runs every 60 s and unloads models idle past their timeout.\n\n### Limits + per-model caps\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_MAX_BODY_SIZE` | `32mb` | Cap on request body — over → 413. Human-readable (`32mb`, `512k`, `1g`) or plain int bytes. |\n| `PREDICTALOT_TIMESFM_MAX_CONTEXT` | `2048` | Compile-time max context for TimesFM. Multiple of 32. |\n| `PREDICTALOT_TIMESFM_MAX_HORIZON` | `512` | Compile-time max horizon for TimesFM. Multiple of 128. |\n| `PREDICTALOT_MOIRAI_MAX_CONTEXT` | `4000` | Wrapper context length for Moirai-2 (shorter inputs zero-padded). |\n| `PREDICTALOT_MOIRAI_MAX_HORIZON` | `512` | Upper horizon for Moirai-2. Wrappers are cached per requested horizon. Multivariate requests have a 64-step native limit. |\n\n### Sundial sidecar + logging\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_SUNDIAL_SOCK` | `/tmp/predictalot/sundial.sock` | Unix socket the main service uses to reach the sundial sidecar venv. |\n| `PREDICTALOT_SUNDIAL_NUM_SAMPLES` | `64` | Monte-Carlo samples per sundial forecast (more = smoother quantiles, linearly slower). |\n| `PREDICTALOT_SUNDIAL_READY_TIMEOUT` | `60s` | How long to wait for the sundial sidecar on first request. |\n| `PREDICTALOT_LOG_LEVEL` | `INFO` | `DEBUG` / `INFO` / `WARNING` / `ERROR`. |\n\n## Ports\n\n| Port | Service |\n|---|---|\n| 8080 | HTTP API + MCP (`/mcp`) on the same port |\n\nContainer binds `0.0.0.0:8080` by default. Use `-p` at `docker run` for whatever host mapping you want.\n\n## Management\n\n```bash\ndocker logs -f predictalot            # tail logs (sundial worker stderr tagged [sundial])\ndocker stop predictalot               # stop\ndocker rm predictalot                 # remove\ndocker pull psyb0t/predictalot:latest # update\n```\n\nInspect model state without stopping anything:\n\n```bash\ncurl -s http://localhost:8080/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n```\n\nFree every resident foundation model early when the next task does not need it:\n\n```bash\ncurl -s -X POST http://localhost:8080/v1/models/unload \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n```\n\nThe endpoint returns `409` while a foundation forecast is active. A forecast body with `\"unload\": true` instead schedules that model for teardown after concurrent forecasts using the same model have finished.\n\n## OpenClaw / ClawHub Config\n\n```bash\nexport PREDICTALOT_URL=http://localhost:8080\nexport PREDICTALOT_AUTH_TOKEN=<token>   # only if the server requires it\n```\n\nOr via `~/.openclaw/openclaw.json`:\n\n```json\n{\n  \"skills\": {\n    \"entries\": {\n      \"predictalot\": {\n        \"env\": {\n          \"PREDICTALOT_URL\": \"http://localhost:8080\",\n          \"PREDICTALOT_AUTH_TOKEN\": \"<token>\"\n        }\n      }\n    }\n  }\n}\n```\n\n## Public Access via Reverse Proxy (optional)\n\nFor public exposure, terminate TLS at a reverse proxy (Caddy / Traefik / nginx) and combine it with `PREDICTALOT_AUTH_TOKENS`.\n\n```caddy\npredictalot.example.com {\n    reverse_proxy localhost:8080\n}\n```\n\nSet the auth token on the container so even a misconfigured proxy still requires `Authorization: Bearer`. Don't rely on the proxy alone. The same logic applies to Cloudflare Tunnel / Tailscale — the tunnel provides transport security, the bearer token provides app-layer auth.\n\nFile v1.2.0:skill-card.md\n\n## Description:\n\nConnects an agent to a self-hosted forecasting service for time-series predictions, ensembles, and trained tabular forecasts.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[psyb0t](https://clawhub.ai/user/psyb0t)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and analysts use this skill to forecast numeric series with optional covariates or ensembles, and to train tabular models on their own features for direction, value, or quantile predictions.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Forecast or training requests can send sensitive series and features to the configured service.\n\nMitigation: Use only a service you control or trust, prefer HTTPS, and avoid sending proprietary data to an untrusted URL.\n\nRisk: An exposed service can be accessed by anyone who can reach its port if authentication is disabled.\n\nMitigation: Bind to localhost by default, use a strong bearer token, and restrict any wider access through a secure proxy or VPN.\n\nRisk: Deleting a stored tabular model cannot be undone.\n\nMitigation: Confirm the model ID from the service and obtain explicit user confirmation before deletion.\n\nRisk: Using a floating Docker image tag can unexpectedly change the service version.\n\nMitigation: Pin a reviewed image tag or digest rather than using :latest.\n\n## Reference(s):\n\n- [ClawHub skill listing](https://clawhub.ai/psyb0t/skills/predictalot)\n- [Setup guide](references/setup.md)\n- [Model Context Protocol documentation](https://modelcontextprotocol.io)\n\n## Skill Output:\n\n**Output Type(s):** [Text, JSON, Shell commands, Guidance]\n\n**Output Format:** [Markdown guidance and shell examples; JSON forecast responses]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Forecasts may include quantile bands or sample paths; tabular predictions may include probabilities, values, or quantiles. The moirai-2 model is restricted to non-commercial use.]\n\n## Skill Version(s):\n\n1.2.0 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.1.9: 5 files, 17765 bytes\n\nFiles: references/setup.md (7432b), scripts/predictalot.sh (2122b), skill-card.md (2729b), SKILL.md (34038b), _meta.json (130b)\n\nFile v1.1.9:SKILL.md\n\n---\nname: predictalot\ndescription: Self-hosted forecasting/prediction service. Foundation time-series endpoints under /v1/timeseries/<type>/{forecast,forecast/ensemble} + GET .../models — univariate, past/future/both covariates, multivariate, samples — over 5 zero-shot models (chronos-2, timesfm-2.5, moirai-2, toto-1, sundial-base-128m). Plus supervised tabular ML under /v1/tabular/ (9 backends — lightgbm, xgboost, hist-gbt, random-forest, logistic, mlp, svm-rbf, knn, naive-bayes — over direction/value/quantile modes) with train+persist, weighted ensembles, and calibrated/stacking/diversified meta-learners. Unified REST + MCP (streamable-HTTP at /mcp, one tool per (type, model) cell + per-type ensemble + listing) + optional bearer auth. Use when the user wants to forecast a numeric time series (quantile bands or raw sample paths), condition a forecast on known/future covariates, ensemble several forecasters, or train a tabular model on engineered features and predict direction/value/quantiles on the latest snapshot.\nhomepage: https://github.com/psyb0t/docker-predictalot\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🔮\", \"primaryEnv\": \"PREDICTALOT_URL\", \"requires\": { \"bins\": [\"docker\", \"curl\"] } } }\npermissions:\n  network: \"outbound HTTP(S) to the configured PREDICTALOT_URL only (forecast/train/model-management calls + MCP at /mcp)\"\n  shell: \"docker + curl invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# predictalot\n\nSelf-hosted forecasting service — one HTTP container, two model families.\n\n- **Foundation time-series (zero-shot)** — 5 forecasters (`chronos-2`, `timesfm-2.5`, `moirai-2`, `toto-1`, `sundial-base-128m`). Hand them a context window, get quantile bands or raw sample paths. No training step. Routed by forecast type under `/v1/timeseries/<type>/` — `univariate`, `covariates/past`, `covariates/future`, `covariates` (past+future), `multivariate`, `samples`. Each type has `forecast`, `forecast/ensemble`, and a `models` listing.\n- **Tabular ML (supervised)** — 9 learners (`lightgbm`, `xgboost`, `hist-gbt`, `random-forest`, `logistic`, `mlp`, `svm-rbf`, `knn`, `naive-bayes`), all supporting 3 modes (`direction` / `value` / `quantile`). Train on YOUR engineered features, persist server-side by `modelId`, forecast on the latest feature snapshot. Weighted ensembles over stored models plus 3 meta-learners (`calibrated` / `stacking` / `diversified`). Under `/v1/tabular/`.\n- **MCP** — streamable-HTTP tools at `/mcp`. One tool per (FM type, model) cell plus per-type ensemble + listing. Tabular is HTTP-only.\n- **Auth** — optional bearer token (`PREDICTALOT_AUTH_TOKENS` on the server; refuses to start with no tokens unless `PREDICTALOT_ALLOW_NO_AUTH=1`).\n\nFor installation, configuration, and container setup, see [references/setup.md](references/setup.md).\n\n## Security & safety\n\n- **Network-exposed** — predictalot is a plain HTTP + MCP service; anyone who can reach the port can call it. Set `PREDICTALOT_AUTH_TOKENS` to a strong generated secret (`$(openssl rand -hex 32)`) and bind to loopback (`-p 127.0.0.1:8080:8080`) by default. Only expose beyond loopback behind a reverse proxy / VPN, and never with the default/example token.\n- **External transmission** — every forecast/train call sends your time series, engineered feature values, and/or `modelId`s to whatever `PREDICTALOT_URL` points at — that data leaves your host. Point it only at a service you run or explicitly trust; prefer HTTPS.\n- **Consumer-only** — this skill talks to an instance you already run and trusts. It never provisions, starts, or hardens the server; that's the operator's job (see setup.md).\n- **Destructive delete requires confirmation** — `DELETE /v1/tabular/models/{modelId}` permanently removes a trained model and cannot be undone. Only call it against a `modelId` you obtained from a prior `GET /v1/tabular/models` (or a train response) in this session, and get explicit user confirmation before issuing the delete.\n\n## When To Use\n\n- Forecast a numeric time series N steps ahead and get calibrated quantile bands (`0.1`/`0.5`/`0.9`, etc.) — zero-shot, no training.\n- Get raw Monte-Carlo sample paths (via the `samples` type) to compute custom risk metrics / joint distributions across horizon steps.\n- Condition a forecast on covariates: known-history (`covariates/past`), forward-known drivers like a planned promotion or price schedule (`covariates/future`), or both at once (`covariates`).\n- Forecast several correlated channels jointly (`multivariate`).\n- Combine multiple forecasters into a weighted ensemble and inspect each member's individual forecast + applied weight.\n- Train a supervised model on engineered features and predict next-bar direction (`P(up)`), a point value, or quantiles on the latest snapshot.\n- Combine trained tabular models via ensemble or a `calibrated` / `stacking` / `diversified` meta-learner.\n\n## When NOT To Use\n\n- Real-time / streaming forecasts — every endpoint is request/response only.\n- Automatic feature engineering on the tabular side — you supply the features; predictalot does not derive indicators or lags for you.\n- `timesfm-2.5` for accuracy — it is the weakest of the five on every benchmarked dataset. Skip it or set `weights={\"timesfm-2.5\": 0}` in ensembles.\n- Commercial use of `moirai-2` — it ships under CC-BY-NC-4.0 (non-commercial). The other four FMs are Apache 2.0.\n- Covariate/multivariate/samples types on models that don't support them — membership is fixed per type (see below). A non-member `model` → 400.\n- Provisioning or hardening the server from here — this skill is a **consumer**. It talks to an instance the user already runs and trusts; it never launches, escalates, or reconfigures the container.\n\n## Setup\n\nThe container should already be running. Point at it:\n\n```bash\nexport PREDICTALOT_URL=http://localhost:8080\n```\n\nIf the server has `PREDICTALOT_AUTH_TOKENS` set, export a token too:\n\n```bash\nexport PREDICTALOT_AUTH_TOKEN=<your-token>\n# every /v1/* and /mcp request below needs: -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\"\n```\n\n**Verify:** `curl $PREDICTALOT_URL/healthz` returns `{\"ok\": true}`. (`/healthz` is unauthenticated.)\n\nFor install / configuration / env vars / CPU vs CUDA images, see [references/setup.md](references/setup.md).\n\n## Models & Types\n\nFoundation models, and which forecast types each supports (a `model` outside a type's member set → 400):\n\n| Model | Univariate | Multivariate | Cov: past | Cov: future | Cov: both | Samples | License | Recommended for |\n|---|:-:|:-:|:-:|:-:|:-:|:-:|---|---|\n| `chronos-2` | ✓ | ✓ | ✓ | ✓ | ✓ | — | Apache 2.0 | Default all-rounder; fastest on CPU; o\n\nArchive v1.1.8: 5 files, 17784 bytes\n\nFiles: references/setup.md (7432b), scripts/predictalot.sh (2122b), skill-card.md (2987b), SKILL.md (34038b), _meta.json (130b)\n\nArchive v1.1.7: 5 files, 17696 bytes\n\nFiles: references/setup.md (7432b), scripts/predictalot.sh (2122b), skill-card.md (2776b), SKILL.md (34038b), _meta.json (130b)\n\nArchive v1.1.6: 5 files, 17097 bytes\n\nFiles: references/setup.md (7432b), scripts/predictalot.sh (2122b), skill-card.md (1400b), SKILL.md (34038b), _meta.json (130b)\n\nArchive v1.1.5: 5 files, 17754 bytes\n\nFiles: references/setup.md (7432b), scripts/predictalot.sh (2122b), skill-card.md (2824b), SKILL.md (34038b), _meta.json (130b)\n\nArchive v1.1.4: 5 files, 17769 bytes\n\nFiles: references/setup.md (7432b), scripts/predictalot.sh (2122b), skill-card.md (2909b), SKILL.md (34038b), _meta.json (130b)\n\nArchive v1.1.3: 5 files, 17739 bytes\n\nFiles: references/setup.md (7432b), scripts/predictalot.sh (2122b), skill-card.md (2815b), SKILL.md (34038b), _meta.json (130b)","readmeExcerpt":"Skill: predictalot Owner: psyb0t Summary: Self-hosted forecasting/prediction service. Foundation time-series endpoints under /v1/timeseries/<type>/{forecast,forecast/ensemble} + GET .../models — univariate, past/future/both covariates, multivariate, samples — over 5 zero-shot models (chronos-2, timesfm-2.5, moirai-2, toto-1, sundial-base-128m). Plus supervised tabular ML under /v1/tabular/ (9 backends — lightgbm, xgb","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"export PREDICTALOT_URL=http://localhost:8080"},{"language":"bash","snippet":"export PREDICTALOT_AUTH_TOKEN=<your-token>\n# every /v1/* and /mcp request below needs: -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\""},{"language":"bash","snippet":"curl -s $PREDICTALOT_URL/healthz | jq"},{"language":"bash","snippet":"curl -s $PREDICTALOT_URL/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq"},{"language":"bash","snippet":"curl -s $PREDICTALOT_URL/v1/timeseries/univariate/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{"},{"language":"bash","snippet":"# Health (unauthenticated).\ncurl -s $PREDICTALOT_URL/healthz | jq\n\n# List univariate models + their runtime state.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/models \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" | jq\n\n# Zero-shot univariate forecast: 5 steps ahead of one series, chronos-2.\ncurl -s $PREDICTALOT_URL/v1/timeseries/univariate/forecast \\\n  -H \"Authorization: Bearer $PREDICTALOT_AUTH_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n        \"model\": \"chronos-2\",\n        \"context\": [[10,11,12,13,14,15,16,17,18,19,20]],\n        \"config\": { \"horizon\": 5, \"quantileLevels\": [0.1, 0.5, 0.9] }\n      }' | jq"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: predictalot\ndescription: Self-hosted forecasting/prediction service. Foundation time-series endpoints under /v1/timeseries/<type>/{forecast,forecast/ensemble} + GET .../models — univariate, past/future/both covariates, multivariate, samples — over 5 zero-shot models (chronos-2, timesfm-2.5, moirai-2, toto-1, sundial-base-128m). Plus supervised tabular ML under /v1/tabular/ (9 backends — lightgbm, xgboost, hist-gbt, random-forest, logistic, mlp, svm-rbf, knn, naive-bayes — over direction/value/quantile modes) with train+persist, weighted ensembles, and calibrated/stacking/diversified meta-learners. Unified REST + MCP (streamable-HTTP at /mcp, one tool per (type, model) cell + per-type ensemble + listing) + optional bearer auth. Use when the user wants to forecast a numeric time series (quantile bands or raw sample paths), condition a forecast on known/future covariates, ensemble several forecasters, or train a tabular model on engineered features and predict direction/value/quantiles on the latest snapshot.\nhomepage: https://github.com/psyb0t/docker-predictalot\nuser-invocable: true\nmetadata:\n  { \"openclaw\": { \"emoji\": \"🔮\", \"primaryEnv\": \"PREDICTALOT_URL\", \"requires\": { \"bins\": [\"docker\", \"curl\"] } } }\npermissions:\n  network: \"outbound HTTP(S) to the configured PREDICTALOT_URL only (forecast/train/model-management calls + MCP at /mcp)\"\n  shell: \"docker + curl invocations shown in setup.md and this file (container lifecycle, request examples) — no other host access\"\n---\n\n# predictalot\n\nSelf-hosted forecasting service — one HTTP container, two model families.\n\n- **Foundation time-series (zero-shot)** — 5 forecasters (`chronos-2`, `timesfm-2.5`, `moirai-2`, `toto-1`, `sundial-base-128m`). Hand them a context window, get quantile bands or raw sample paths. No training step. Routed by forecast type under `/v1/timeseries/<type>/` — `univariate`, `covariates/past`, `covariates/future`, `covariates` (past+future), `multivariate`, `samples`. Each type has `forecast`, `forecast/ensemble`, and a `models` listing.\n- **Tabular ML (supervised)** — 9 learners (`lightgbm`, `xgboost`, `hist-gbt`, `random-forest`, `logistic`, `mlp`, `svm-rbf`, `knn`, `naive-bayes`), all supporting 3 modes (`direction` / `value` / `quantile`). Train on YOUR engineered features, persist server-side by `modelId`, forecast on the latest feature snapshot. Weighted ensembles over stored models plus 3 meta-learners (`calibrated` / `stacking` / `diversified`). Under `/v1/tabular/`.\n- **MCP** — streamable-HTTP tools at `/mcp`. One tool per (FM type, model) cell plus per-type ensemble + listing. Tabular is HTTP-only.\n- **Model lifecycle** — `POST /v1/models/unload` releases every resident foundation model. A forecast's `unload: true` waits for concurrent forecasts using that model, then tears it down. The MCP equivalent is `unload_models`.\n- **Auth** — optional bearer token (`PREDICTALOT_AUTH_TOKENS` on the server; refuses to start with no tokens unless `PREDICTALOT_ALLOW_NO_AUTH=1"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn79dhvmpjng4rp2jjk8k0v5xx80ccbk\",\n  \"slug\": \"predictalot\",\n  \"version\": \"1.2.2\",\n  \"publishedAt\": 1791644446805\n}"},{"path":"references/setup.md","content":"# predictalot setup\n\nConsumer-facing reference for running / pointing at a predictalot instance. This skill talks to an instance you already run and trust; the notes below cover standing one up if you haven't.\n\n## Requirements\n\n- Docker\n- Optional: NVIDIA GPU + NVIDIA Container Toolkit for the CUDA image (CPU works for all five FMs; `chronos-2` is the fastest on CPU)\n- Disk for model snapshots under `/models` (per-slug sizes ≈ chronos-2 ~120 MB, timesfm-2.5 ~200 MB, moirai-2 ~50 MB, toto-1 ~580 MB, sundial-base-128m ~490 MB) plus trained tabular models under `/models/tabular/<id>/`\n\n## Security & safety\n\npredictalot is a plain HTTP + MCP service — anyone who can reach the port can call it. Before any non-local / shared deployment:\n\n- Set `PREDICTALOT_AUTH_TOKENS` to a strong generated secret, e.g. `$(openssl rand -hex 32)` — never leave it at a placeholder/default value.\n- Bind to loopback by default (`-p 127.0.0.1:8080:8080`); only expose beyond loopback behind a reverse proxy / VPN (see \"Public Access via Reverse Proxy\" below).\n\n## Quick Install\n\n### CPU\n\n```bash\ndocker run -d --name predictalot \\\n  -v $HOME/predictalot-models:/models \\\n  -e PREDICTALOT_AUTH_TOKENS=$(openssl rand -hex 32) \\\n  -p 127.0.0.1:8080:8080 \\\n  psyb0t/predictalot:latest\n```\n\n### CUDA\n\n```bash\ndocker run -d --name predictalot --gpus all \\\n  -v /srv/predictalot-models:/models \\\n  -e PREDICTALOT_AUTH_TOKENS=$(openssl rand -hex 32) \\\n  -e PREDICTALOT_DEVICE=cuda \\\n  -e PREDICTALOT_PRELOAD=chronos-2,toto-1,sundial-base-128m \\\n  -p 127.0.0.1:8080:8080 \\\n  psyb0t/predictalot:latest-cuda\n```\n\n`PREDICTALOT_DEVICE=auto` (the default) picks CUDA when available, else CPU.\n\n### docker-compose\n\n```yaml\nservices:\n  predictalot:\n    image: psyb0t/predictalot:latest\n    ports:\n      - \"127.0.0.1:8080:8080\"\n    environment:\n      PREDICTALOT_AUTH_TOKENS: \"${PREDICTALOT_AUTH_TOKENS:?set to a generated secret, e.g. openssl rand -hex 32}\"\n      PREDICTALOT_PRELOAD: chronos-2,toto-1\n    volumes:\n      - ./predictalot-models:/models\n    restart: unless-stopped\n```\n\nGenerate the token once and export it before `docker compose up` (e.g. `export PREDICTALOT_AUTH_TOKENS=$(openssl rand -hex 32)`) — never commit a real token or ship the example value as-is. The token MUST be changed before any non-local/shared deployment.\n\n**Verify:** `curl http://localhost:8080/healthz` returns `{\"ok\": true}` once boot is done.\n\n**Snapshots:** foundation-model weights download into `/models/<slug>/` on first use (or at boot when listed in `PREDICTALOT_PREFETCH`). Bind-mount `/models` so restarts are no-ops. Trained tabular models persist under `/models/tabular/<id>/`.\n\n## Environment Variables\n\nAll runtime config is `PREDICTALOT_*` — set via `docker run -e`, compose `environment:`, or a k8s ConfigMap.\n\n### Auth + bind\n\n| Var | Default | What it does |\n|---|---|---|\n| `PREDICTALOT_AUTH_TOKENS` | (empty) | Comma-separated bearer tokens. Empty = **refused at startup** unless `PREDICTALOT_ALLOW_NO_AUTH=1`. When set, `"},{"path":"skill-card.md","content":"## Description:\n\nHelps agents forecast time series and train or query tabular prediction models through a user-configured, self-hosted predictalot service.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[psyb0t](https://clawhub.ai/user/psyb0t)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and analysts use this skill to request forecasts, uncertainty bands, and sample paths from a trusted predictalot endpoint, or to train and query tabular prediction models on their own features.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Forecasting and training data are sent to the configured service.\n\nMitigation: Use only an endpoint you control and trust; do not send proprietary data to an untrusted URL.\n\nRisk: A publicly reachable service can expose forecasting and model-management operations.\n\nMitigation: Keep the service bound to localhost unless protected by TLS, a strong bearer token, and network access controls.\n\nRisk: An unpinned Docker image can change between deployments.\n\nMitigation: Pin the image to a reviewed version or digest before production use.\n\nRisk: Deleting a stored tabular model is irreversible.\n\nMitigation: Verify the model ID and obtain explicit confirmation before deletion.\n\n## Reference(s):\n\n- [predictalot on ClawHub](https://clawhub.ai/psyb0t/skills/predictalot)\n- [Setup and security guidance](references/setup.md)\n- [Model Context Protocol documentation](https://modelcontextprotocol.io)\n\n## Skill Output:\n\n**Output Type(s):** [Text, Markdown, Shell commands, Configuration guidance]\n\n**Output Format:** [Markdown guidance, shell commands, and JSON forecast responses]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Forecasts may include quantile bands or sample paths; tabular predictions depend on the selected mode.]\n\n## Skill Version(s):\n\n1.2.2 (source: ClawHub release metadata)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment."}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":1663,"uniquenessScore":42,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-10T23:45:17.789Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-10T23:45:17.789Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T03:54:31.182Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}