Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,72 @@
# Changelog

## v0.36.0 — 2026-09-01 — the night is not runway

**The forecast learns when you work, not just how much.** Claude Code can
work around the clock; the human cannot, and every projection this script
made burned flat through the night. The bill came in four places: a
`7d dry ~Thu 03:00` that is a false alarm at 23:00 and a missed warning at
09:00; an L1 blend projecting a hot day's rate through eight idle hours;
"spend it" appearing at 23:00 for a surplus no waking hour can reach; and
a ration dividing the week by windows you sleep through. The corpus
already held the answer — burn credited by the envelope pass carries a
timestamp, and hours that never burn across weeks are rest.

`forecast.cache` gains `hour_profile`: 24 burn multipliers, local hour
`"0"`..`"23"`, mean 1, so the rate at hour h is `weekday_rate x mult[h]`
and integrating any whole day reproduces the weekday total exactly. Same
envelope pass, same 14-day EWMA half-life, and today is excluded from it —
a day that has only reached noon reports every evening hour as rest. The
shape is floored at 0.1 and renormalized at BUILD time, so every reader
sees the same numbers: a rest hour projects a tenth of a uniform hour,
never zero, which is what the occasional overnight autonomous run costs.
The field is additive, so `schema` stays 2; ccpace v0.4.0 writes and reads
the identical model (`docs/api/state-dir.md` is the contract, down to
`REST_MULT_MAX = 0.25`).

**Absent or broken, the walk is exactly what it was.** The shape is read
under one validation — 24 keys, each numeric in `[0, 24]`, mean in
`[0.9, 1.1]`, `days_history >= 14` — and anything else walks flat. A bad
hour shape narrows no clause and silences nothing; only the weekday guards
can take the walk's voice away, which is the same division of labour the
corrupt-profile guard has always had.

**The walk steps hours now, and the L1 blend ends where it says it does.**
Day-sized steps could not integrate a rate that changes at 08:00. They
also let the recent-24h blend, once it started mid-day, run to the end of
the day holding `now + 24h` — up to 47 hours of a hot day's rate on a
sentence that claims 24. The blend is tested at each hour segment now, so
it ends at exactly 24h out, and both tools reading this cache walk the
same horizon. On a calm 10%/day profile with a 40% trailing day, that is
`lands ~70%` where it used to be `~73%`.

**`~6 awake` beside the window count.** The budget line ends up with three
clauses and each answers a different question: `budget ~9✕5h left · ~6
awake · even 3.8%/win · lands ~52%`. The awake count is the windows that
fall in hours you have actually burned in; it appears only when it cuts
the calendar count, and it is there to name the denominator beside it —
`even` divides by the awake windows when the clause is present. A ration
you can only hit by not sleeping is not a ration. At zero awake windows
both clauses go and the landing stands alone. Row 2 is unchanged: columns
are precious, and the strip beside it still counts every window ahead.

**"Spend it" costs awake hours now.** The surplus feasibility check
(`pct_per_window x time / 5h`, capped by the current window's headroom)
counts only awake seconds in each leg. The current-window leg keeps its
own cap; unlearned, the arithmetic is identical to before. A 35% surplus
with 20 hours to the reset and 22 of the next 24 at rest reads `~35%
expires even at full burn` instead of `spend it`.

**`report` states the rhythm it walks.** One line, when learned:
`rhythm: rest ~00:00-08:00 · 16h awake/day (learned)` — the longest
circular run of rest hours, printed at three or more, because sleep wraps
midnight and a run cut there reads as two short ones. An account that
never stops gets `rhythm: no rest learned — burns around the clock`.

Dry warnings need no new words: the shaped walk moves the dry TIME out of
sleep by itself, which was the early-warning fix all along. The strip fold
`(✕N)` and the windows-ahead countdown are untouched.

## v0.35.0 — 2026-08-27 — a directory is where a sample landed, not who it belongs to

**A directory is where a sample landed, not who it belongs to.** The
Expand Down
17 changes: 17 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,23 @@ fb 91% vs 7d 55% the MODEL caps first, not the account: switch, and the
~62% will expire on pace to strand a large chunk of the subscription
```

The budget line is the one sentence with three clauses, and each answers a
different question:

```
budget ~9✕5h left · ~6 awake · even 3.8%/win · lands ~52%
count awake ration prediction
```

`~N awake` is how many of those windows fall in hours this account has, week
after week, actually burned in (`hour_profile`, `mult ≥ 0.25`). It appears
only when it *cuts* the count, and it is there to name the denominator beside
it: `even` divides by the awake windows when the clause is present and by all
of them when it is not. A ration you can only hit by not sleeping is not a
ration. At zero awake windows both clauses go and the landing stands alone.
Row 2 is unchanged — it carries the landing, and falls back to the ration
using the same denominator the long form would.

## Requests

The API is asked for what the protocol does not hand us, and no more.
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,15 +390,15 @@ clauses about one window can never disagree.
|--------|-----------------------------------|
| `! fb capped ~Thu 07:00` | The weekly limit scoped to *this session's model* hit 100%: that model is gone until then. The long form names a model that still has room. |
| `! 5h caps ~05:18` | Linear projection off this window's own pace: you hit the wall before the reset. Long form adds how long you'd sit blocked. |
| `! 7d dry ~Thu 09:00 · hard stop` | The learned per-weekday burn profile (EWMA over your own history), not a straight line — your heavy Tuesday counts more than an average. Tail says what happens at 100%: extra billing, or a stop. |
| `! 7d dry ~Thu 09:00 · hard stop` | The learned per-weekday burn profile (EWMA over your own history), not a straight line — your heavy Tuesday counts more than an average, and the learned hour-of-day shape puts the dry point where you are awake to act on it instead of at 03:00. Tail says what happens at 100%: extra billing, or a stop. |
| `! fb 91% vs 7d 55% · go op` | **The relation** between two badges: the *model* caps before the account does. Switching models buys the week's remaining capacity back; the roomiest other `weekly_scoped` limit in the payload gets named. |
| `+ 7d rebased 53%→12%` | Utilization fell *inside* one window instance: burn never runs backwards, so this is a plan change or an out-of-band reset. The projections restart; the ledger still draws the old period. |
| `+ last 5h of the week · 47% unused` | The 7d window resets inside this 5h window: no later window exists to spend the remainder through. |
| `+ 47% unused · spend it` (or `· ~33% expires even at full burn`) | Expiring surplus, feasibility-checked against your learned `pct_per_window`: "spend it" only when full-tilt burn can actually consume it. |
| `+ 5h ~40m left · 70% unused` | Said only when the *week* is stranding capacity — an unspent 5h window is otherwise headroom, not waste, since the 5h window is a rate limit and not a budget. |
| `+ ~62% will expire · go heavier` | On pace to strand a large chunk of the subscription. Speaks only to an engaged, unsqueezed session. |
| `+ work 5h[8%] free` | A sibling account in the same shared home is idle while this one is pinned. |
| `19✕5h left · lands ~52%` | The calm budget: runway plus the destination. The long form carries all three clauses — `budget ~19✕5h left · even 1.1%/win · lands ~52%` — and names each for what it is: `even N%/win` is the **ration** (spend that per window and the pool lands exactly on 100), `lands ~N%` is the **prediction** (spend like you have been and you end up here). Row 2 keeps the landing, because the count is already drawn on the strip beside it and the ration is surplus ÷ count, but where the week ends up is nowhere else on screen; with no projection yet the row falls back to the ration. 19 counts the windows **after** the one you are in — the window you are inside is where you are, not what you have left, and it is already drawn as `▮` and priced by `5h[38%]`. The count holds steady inside a window and steps down by one at each rollover. It wears no sigil — `!` and `+` interrupt, the week's resting reading does not. |
| `19✕5h left · lands ~52%` | The calm budget: runway plus the destination. The long form carries all three clauses — `budget ~19✕5h left · ~13 awake · even 1.6%/win · lands ~52%` — and names each for what it is: `even N%/win` is the **ration** (spend that per window and the pool lands exactly on 100), `lands ~N%` is the **prediction** (spend like you have been and you end up here). `~13 awake` is how many of those windows fall in hours you have actually burned in (the learned `hour_profile`); it shows only when it cuts the count, and `even` then divides by it — a ration you can only hit by not sleeping is not a ration. Row 2 keeps the landing, because the count is already drawn on the strip beside it and the ration is surplus ÷ count, but where the week ends up is nowhere else on screen; with no projection yet the row falls back to the ration. 19 counts the windows **after** the one you are in — the window you are inside is where you are, not what you have left, and it is already drawn as `▮` and priced by `5h[38%]`. The count holds steady inside a window and steps down by one at each rollover. It wears no sigil — `!` and `+` interrupt, the week's resting reading does not. |

`--notice off` keeps row 3 quiet; `--advisor off` silences both. `--check`
and `--week` print the long form, since a terminal command has a whole
Expand All @@ -419,6 +419,7 @@ usage report - work (last 28d, 79 samples)

5h windows closed: 3 avg 95% at close 2 hit the cap
exchange rate: one full 5h window = ~10.46% of the week (~9.6 windows/week, learned)
rhythm: rest ~00:00-08:00 · 16h awake/day (learned)

week in progress: 5% used, resets Mon 08-03 23:59
```
Expand Down
50 changes: 48 additions & 2 deletions docs/api/state-dir.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ read concurrently.

- Contract version: **2** (bump on any breaking layout/field change; this
file is the changelog)
- Synced with: statusline.sh v0.35.0
- Synced with: statusline.sh v0.36.0
- Permissions: the script runs under `umask 077` — files are owner-only.
Caches hold account PII (email, uuid, org names).

Expand Down Expand Up @@ -56,7 +56,7 @@ a writer that cannot honor all of them must use its own dir instead:
usage.cache last /api/oauth/usage response + fetched_at
profile.cache last /api/oauth/profile response (24h TTL)
prepaid_credits.cache prepaid balance + fetched_at (5m TTL)
forecast.cache learned weekday burn profile (hourly rebuild)
forecast.cache learned weekday + hour burn profile (hourly rebuild)
week.cache week-row cells: 7d per-window + 5h per-half-hour burn (rebuilt when usage.jsonl grows)
stdin_seen last stdin rate_limits pair logged (`5h|7d epoch`) — dedupe for source:stdin samples
usage.jsonl append-only usage/session event log
Expand Down Expand Up @@ -115,6 +115,7 @@ Output of the hourly usage.jsonl scan (EWMA half-life 14 days):
"recent_24h": 14.20, "recent_48h": 22.10,
"pct_per_window": 11.83,
"weekday_profile": {"0": 5.1, "1": 27.3, "…": 0, "6": -1},
"hour_profile": {"0": 0.09, "…": 1.83, "23": 1.24},
"scoped_name": "Fable", "scoped_recent_24h": 76.00,
"scoped_profile": {"0": 5.7, "1": 23.5, "…": 0, "6": -1},
"cost": {"usd_24h": 12.40, "usd_7d": 84.10,
Expand Down Expand Up @@ -162,6 +163,51 @@ inside the same 5h window; `-1` until enough paired burn has been
observed (>= half a window). Snapshots are partitioned by `account.uuid`
before aggregation.

`hour_profile` is the same account on the other axis: 24 keys, local hour
`"0"`..`"23"`, values are burn MULTIPLIERS with mean 1. The instantaneous
rate at hour h is `weekday_rate * mult[h]` (%/day), so integrating any
whole day reproduces the weekday total exactly — the weekday says what a
Tuesday costs, the hour says when in it. The field is additive, so
`schema` stays 2: a reader that has never heard of it walks flat, which is
what every reader did before it existed.

Built inside the same envelope pass — each credited delta is also credited
to `(local_day, local_hour)`, EWMA-weighted by day age with the same
14-day half-life, and today is EXCLUDED, because a day that has only
reached noon reports every evening hour as rest. The weighted share per
hour becomes `m[h] = max(share[h] * 24, 0.1)`, then the whole vector is
scaled so the mean is exactly 1, rounded to 2 decimals. Floored AND
renormalized at BUILD time so every reader sees the same numbers instead
of each applying the hedge its own way; a floored hour therefore prints
slightly under the floor (`0.09`) once many hours were floored — that is
correct, do not re-floor on read. The 0.1 floor is the hedge for the
occasional overnight autonomous run: a rest hour projects a tenth of a
uniform hour, never zero. Omitted entirely when no weighted burn exists.

Use it only if it is an object with all 24 keys, every value numeric in
`[0, 24]`, and the mean in `[0.9, 1.1]` — plus the `days_history >= 14`
gate every learned surface shares. Invalid or absent means FLAT (mult
== 1), never silence: a bad hour shape must not take a good forecast away,
and only the weekday guards do that. Both readers walk the profile by
stepping local-hour boundaries, and the recent-24h blend that opens the
walk ends at exactly 24h out.

```
REST_MULT_MAX = 0.25 an hour below this is a REST hour
```

An AWAKE WINDOW is a 5h window you are awake for. Over the span
`[now + five_secs, now + seven_secs]` — the same span the windows-ahead
count covers, with `five_secs = 0` when no 5h window is live —
`awake_secs` is the part of it falling in local hours with `mult >=
REST_MULT_MAX`, and `awake = min(windows_ahead, ceil(awake_secs /
18000))`; it ceils for the reason windows-ahead does, a partial window
being still spendable. A surface names the awake count only when the
shape is learned, `windows_ahead >= 1`, and `awake < windows_ahead` — an
equal count would spend a clause to say nothing. At `awake == 0` both the
awake clause and the ration that would divide by it go away; the landing
still speaks.

`scoped_*` mirror the all-model fields for the `weekly_scoped` limit —
the per-model weekly cap, `Fable` at time of writing. `scoped_name` is
the scope the profile is ABOUT (`null` while unobserved); burn is
Expand Down
12 changes: 10 additions & 2 deletions skills/usage-insight/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,18 @@ the deep-dive version of what the advisor row says in one sentence.
```json
{"days_history": 21, "recent_24h": 14.2,
"pct_per_window": 11.83,
"weekday_profile": {"0": 5.1, "1": 27.3, "6": -1}}
"weekday_profile": {"0": 5.1, "1": 27.3, "6": -1},
"hour_profile": {"0": 0.09, "9": 1.83, "23": 1.24}}
```

- `weekday_profile`: learned percent-of-7d-quota burned per weekday
(0=Sun..6=Sat), EWMA half-life 14 days; `-1` = never observed.
Cold start below 14 `days_history` — say projections are unlearned.
- `hour_profile`: burn multiplier per local hour, mean 1 — the rate at
hour h is `weekday_profile[dow] x hour_profile[h]`. Below 0.25 is a
REST hour; absent or malformed means flat, never silence. Hours the
account sleeps through are not capacity: count only those at or above
0.25 when you say what is spendable before a reset.
- `pct_per_window`: the exchange rate — 7d percentage points one fully
burned 5h window costs THIS account. `-1` = unlearned. A week holds
roughly `100 / pct_per_window` full windows.
Expand All @@ -66,7 +72,9 @@ the deep-dive version of what the advisor row says in one sentence.
hours to its reset, the burnable maximum is roughly
`pct_per_window x (T / 5h)` (capped by the current 5h window's own
headroom). If that is less than S, do NOT say "use it up" — say how
much expires no matter what.
much expires no matter what. T is AWAKE hours once `hour_profile` is
learned: "spend it" at 23:00 is advice to burn a week through eight
hours of sleep.
- With `pct_per_window = -1`, state facts, not advice.
- A scoped limit (`limits[]`) at 100% means that model is unavailable:
the useful fact is its `resets_at`, not the percentage.
Expand Down
Loading
Loading