// askcal • a daily scheduler
Your inbox, ranked by regret.
Not everything in your inbox deserves your attention. Askcal works out what will actually cost you something if you ignore it — then builds your day around that.
regretnoun
what it costs you to have ignored something. Not how loudly it asked, and not when it arrived.
// how it works
Twelve arrived. Four of them wanted something.
The same morning, twice. First as a list, in the order it landed. Then as a day, in the order it costs you — and the things that wanted nothing from you are not on it at all.
// what arrived • by time
// what it became • by consequence
The listening update and the statement are not here — nothing is owed on either, so neither became work. They stay in the inbox to be read when there is time.
Fig. 01
The model reads. Arithmetic scores.
Ask a language model for a number between 0 and 100 and you get a different answer on Tuesday than on Monday for the same message. So it is never asked. It extracts what it can genuinely judge — the stakes, the sender, whether there is a concrete task, when it is due — and four terms of arithmetic turn those into the score.
→ becomes a task on your day
Not a mock-up. The constants, both floors and the
clamp are the ones in regret.py and
config.py; this is that function, running
here. Every message is invented — the shapes are real, the mail is not.
// what it refuses
Most mail is not work.
Scoring a message and making
work out of it are different decisions. The first version of the second
one was, in effect, “the model said yes” — five clauses, three of them
almost always true, and a two-item blacklist that the loudest mail in
any inbox walks straight through: a blast only has to be labelled
money_loss to pass. Confidence and the
score were both already computed and never once consulted. They are
floors now.
| the mail | action | consequence | conf | score | verdict |
|---|---|---|---|---|---|
| 50 new roles for you this week | ✗ | none | — | 0 | a digest is reading, not work |
| Someone viewed your profile | ✗ | social | — | 18 | social is never a task |
| Payment successful — card debited | ✗ | none | — | 10 | money that already moved |
| Term fee — payable Friday | ✓ | money_loss | 0.91 | 64 | → task, Thursday |
| Revision on the deck, by Thursday | ✓ | client_trust | 0.88 | 85 | → task, today |
Two of five. The sender is deliberately not a gate: an assignment from a no-reply system is as real as a client writing by hand, and filtering automated senders out silently dropped exactly the work that mattered — a bug this project has shipped once and does not intend to ship twice.
// the arithmetic
Four terms, one multiplier, one dampener.
All of it, printed. A score you can print is a score you can argue with — and when something ranks oddly you read why in four lines rather than asking a model to explain a judgement it has already forgotten making.
consequence
- opportunity_loss
- 35
- grade_loss
- 35
- client_trust
- 35
- money_loss
- 30
- social
- 12
- none
- 4
A missed assessment and a missed exam cost the same thing. Money is a shade below both — it is recoverable.
deadline • hours out
- ≤ 24, or overdue
- 40
- ≤ 48
- 28
- ≤ 96
- 18
- ≤ 168
- 10
- beyond a week
- 4
- overdue > 72 h
- 10
- no deadline
- 0
Overdue lands in the hottest bucket and then cools, so a thing you missed last month stops pinning the day at maximum forever.
sender
- client
- 12
- recruiter
- 12
- professor
- 8
- automated_system
- 6
- peer
- 4
- other
- 0
- newsletter
- −20
Automated is worth points, not a penalty — assessment links and fee notices arrive from no-reply addresses exactly as noise does.
and the rest
- action_required
- 10
- per carry-forward
- 6
- track weight
- 0.5–1.5
- conf < 0.40
- ×0.6
- task floor
- 25
- conf floor
- 0.60
- clamp
- 0–100
Carrying a task adds to it. The thing you keep pushing to tomorrow is usually the thing worth doing, and a score frozen on arrival can never say so.
// the day
Ranking is easy. Fitting it into a Thursday is not.
Unscheduled • 1 — the portfolio refresh needs 180 minutes and no gap left on this day is that long. Surfaced, never silently dropped and never double-booked on top of something else.
A ranked list is still a list. What survives the gates has to live in the hours you actually have.
Busy blocks come from your real calendar and are hard edges — the planner may not touch them and is never asked to negotiate. Work is placed highest-consequence-first into the earliest gap that fits it, so the brand deck outranks everything here and still starts at 11:30: the half hour before the pin could not hold ninety minutes, and something smaller took it instead.
A pin is an edge like any other. Nothing is ever placed into an hour that has already gone.
// in your words
A code review is work. It was filed under Design.
Askcal shipped with five categories baked into the database, the prompt and the app. They described a guess at somebody's life rather than anybody's actual one, and asked to pick one of five for a pull-request review, the model picked the closest — which was wrong. Tracks are rows you name now, and the sentence you write is what the classifier reads, over any assumption it has about what the name usually means.
Anything from my team or about a pull request, even when I'm not working.
Newsletters, digests, anything I read on a train and never act on.
the pull-request review now lands in Work • one edit • no migration, no deploy, no rebuild
// what it cost
The decisions worth defending.
Every one of these replaced something that already worked. That is the expensive kind.
01 • secrets
Encryption is a column type, not a call site.
A Google refresh token is a long-lived key to an entire mailbox and calendar. Encrypting it wherever it happens to be written means the one place that forgets writes plaintext into a column everything else assumes is safe. It is a database type instead, so forgetting is not on the menu. Stored values carry a version prefix, which is what let the key be rotated without taking a single mailbox offline.
02 • the classifier
Measured, not eyeballed.
Prompt changes used to be validated by waiting for real mail and seeing whether it looked right — which finds a regression days later, on the one message that mattered. A golden set of twelve cases, each carrying the rule it exists to pin, answers the same questions every time. Two of them found errors in the fixture before they found any in the prompt.
03 • the same bug, four times
State that decides what reaches you, applied invisibly.
A classifier silently unconfigured. An inactive track blocking every task under it. Carried work losing its day. Auto-task rules that only ever ran once, at classification, so turning a track on did nothing for mail already seen. Four disguises, one shape — and everything added since has to answer for it.
04 • migrations
Every step could be undone.
Three schema changes each added the new shape beside the old one and backfilled, so every step deployed alone and could roll back. The columns came out much later, in a migration whose own docstring admits it restores the shape and not the data.
05 • the one that got away
A dropped column, and a 500 on every request.
One migration dropped a column the model still declared. Every authenticated request failed for as long as it took to notice, which was an evening. The test suite was green throughout, and correctly so — it is deliberately database-free, which is what makes it fast enough to run on every change and also what makes model-versus-schema drift invisible to it. There is a command that catches exactly this. It is not in CI yet.
06 • notifications
A push that says nothing teaches you to swipe it away.
The morning digest and the evening nudge fired fixed text — “your day is ready” — which is a reminder that something exists, not a reason to open it. Both are written server-side now, in the same call as the screen behind them, because a push promising three deadlines that opens onto a screen showing two is worse than no push at all.
// the whole of it
Stop asking what to do next. Let the cost decide.
Askcal is not a product and there is nothing to sign up for. It runs, it is deployed, and the code is open.