From 2ee633f5eead25dd6c7d524c476f6980f6aae296 Mon Sep 17 00:00:00 2001 From: lachtan Date: Tue, 15 Sep 2026 12:18:52 +0200 Subject: [PATCH] skill usage --- skills/usage/SKILL.md | 95 +++++++++-------------- skills/usage/scripts/ollama_usage_poll.py | 5 +- 2 files changed, 40 insertions(+), 60 deletions(-) diff --git a/skills/usage/SKILL.md b/skills/usage/SKILL.md index 63fe7cc..7b3ed67 100644 --- a/skills/usage/SKILL.md +++ b/skills/usage/SKILL.md @@ -1,9 +1,10 @@ --- name: usage description: > - How much of the Ollama Cloud plan has been spent — current session and weekly - usage per model, and reports over the continuously sampled history. - Triggers on: "ollama usage", "usage history". + How much of the Ollama Cloud plan has been spent — session and weekly usage, + per-model request counts, and delta reports over the continuously sampled + history. Triggers on: "ollama usage", "ollama quota", "ollama credits", + "ollama limits", "how much of the ollama plan is left", "ollama usage report". --- # Usage @@ -16,80 +17,57 @@ Shows Ollama Cloud credit usage for the current API key. uv run skills/usage/scripts/ollama_usage.py ``` -The script reads `OLLAMA_API_KEY` from the `workspace/.env` file (created by -the user). If it is missing or the key fails (401/403), tell the user — -never scrape the website. +The key comes from `OLLAMA_API_KEY` — the environment first, then the +`workspace/.env` file (created by the user). If it is missing or fails +(401/403), tell the user — never scrape the website. ## Output -Format (script prints it, present it to the user as-is — same lines, same -order; translate the labels into the user's language, keep the numbers -exact; no extra model info on the Session/Weekly lines): +Present the script's lines as-is — same lines, same order; translate the labels +into the user's language, keep the numbers exact. ```text Ollama Cloud usage Session: %, resets HH:MM TZ (in H h M min) -Weekly: %, resets in Y days +Weekly: %, resets in Models (request count, weekly window): : ``` -The per-model breakdown lives only in the "Models" section — never inline -on the Session/Weekly lines. +Per-model numbers belong only under "Models", never inline on the Session or +Weekly line. -Times are printed in the **server's local zone** (`Europe/Prague`), taken from -the system — no zone is hardcoded. The session line loses its reset clause when -the history holds no rollover to anchor the window; that is correct output, -not a failure. +The Session line has two other shapes, both correct output and not a failure: +the percentage alone (the history holds no rollover to anchor the window), and +`window expired — the next one starts with the next request`. + +Times are printed in the server's local zone, taken from the system — no zone +is hardcoded. ## Reset times `/api/usage` carries **no reset timestamps**, neither in the body nor in the -response headers (re-checked 2026-09-15). Both are derived. +response headers (re-checked 2026-09-15). Both countdowns are derived. -**Weekly:** next Monday 00:00 UTC, `until_next_monday()`. Matches the dashboard. +- **Weekly:** next Monday 00:00 UTC, `until_next_monday()`. Matches the dashboard. +- **Session:** a 5-hour window anchored by the **first request after the previous + one ran out**, not a fixed grid. So `session_window_end()` takes the newest + rollover in `samples` and adds 5 h, and nothing is extrapolated past it — once + the window runs out there is no next time until a request opens one. -**Session: a 5-hour window anchored by the first request after the previous one -ran out** — not a fixed grid. The length comes from -[ollama.com/blog/transparent-pricing](https://ollama.com/blog/transparent-pricing): -the new plans dropped the "5-hour or weekly limits" this key still has. +**The reset is never guessed.** With no rollover in the history the Session line +carries the percentage alone; a confident wrong number is worse than none. -How the anchoring was established on 2026-09-15: usage sat unchanged at -0.077/21 requests through 05:00 UTC — a fixed grid would have zeroed it there -and the poller would have recorded it — and only reset when a request arrived -at 06:00, after a 93-minute pause. Reconstructing the agent's activity gives a -consistent chain: window 00:00–05:00, then 06:00–11:00, each opened by the -first request after the previous expiry. A fixed grid would additionally -require that request to land exactly on a boundary by chance. - -So `session_window_end()` takes the **newest rollover in `samples`** and adds -5 h. A rollover sample marks the start of a new window, not a boundary that was -due anyway, which is why nothing is ever extrapolated past it: once the window -runs out, the output says the next one starts with the next request rather than -naming a time. - -That it is a window and not a rolling counter was measured too — usage dropped -from 0.077/21 to 0.0/`{}` at once; a rolling counter decays gradually. - -**The reset is never guessed.** An earlier version assumed a calendar hour and -printed "resets in 31 minutes" while the dashboard said "Resets in 2 hours" — -a confident wrong number is worse than none. With no rollover in the history, -the Session line carries the percentage alone. - -Do not compare our countdown against the dashboard's to the hour: the dashboard -rounds an unknown way (it showed "4 hours" and "3 hours" seven minutes apart), -which is why the output prints the wall-clock time too. - -**If the model is wrong, the report shows it.** `Rollover gap:` lines compare -consecutive rollovers against the 5 h window — gaps longer than the block -confirm request-anchoring, a gap exactly equal to it across a long idle stretch -would point back to a fixed grid. +Do not calibrate our countdown against the dashboard's: it rounds an unknown way +(it showed "4 hours" and "3 hours" seven minutes apart), which is why the output +prints the wall-clock time too. ## Continuous sampling A cron job runs `scripts/ollama_usage_poll.py` every minute and appends to -`db/ollama_usage.sqlite` whenever anything changed (table `samples`; table `meta` -records every poll, so a gap can be told apart from a failed poll). +`db/ollama_usage.sqlite` (table `samples`) whenever anything changed. Table +`meta` holds a single row — timestamp and status of the last poll — so a report +that has not moved can be told apart from a dead poller. For a delta report over that data: @@ -101,9 +79,10 @@ Default window is the last 24 hours. Per-model **request counts** are the exact figure there — `limits.*.usage` has a resolution of 0.1 %, so short-interval percentage deltas are noise. -The report ends with `Rollover gap:` lines and a `Session window:` line — -when the window in progress started, when it ends, and how the observed -rollovers line up against the 5 h length. +The report closes with a `Session window:` line and `Last poll:`. From the second +recorded rollover on it also prints `Rollover gap:` lines, which test the window +model: a gap longer than 5 h confirms request-anchoring, one exactly equal to it +across a long idle stretch would point back to a fixed grid. ## Notes @@ -114,4 +93,4 @@ rollovers line up against the 5 h length. - `activity.cost` returns $0.00000 on the Pro plan — broken, omitted from the output. - The `~/.ollama/id_ed25519` key does not work — only an API key minted at - ollama.com/settings/keys. \ No newline at end of file + ollama.com/settings/keys. diff --git a/skills/usage/scripts/ollama_usage_poll.py b/skills/usage/scripts/ollama_usage_poll.py index 329febb..3eed35e 100644 --- a/skills/usage/scripts/ollama_usage_poll.py +++ b/skills/usage/scripts/ollama_usage_poll.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 """Sample Ollama Cloud usage into db/ollama_usage.sqlite. Run from cron every minute. -Writes a `samples` row only when something changed; `meta` records every poll so a -gap in `samples` can be told apart from a poll that failed or never ran. +Writes a `samples` row only when something changed; `meta` holds a single row with +the last poll's timestamp and status, so a history that stopped moving can be told +apart from a poller that stopped running. """ from __future__ import annotations