Kairos
The right moment, caught before it passes.
Kairos (καιρός) is the ancient Greek word for the right moment, as opposed to chronos, which is time merely passing. Personified he is a young god with a long lock of hair over his forehead and nothing at all at the back of his head: you can catch him as he comes toward you, and never once he has gone by.
- Bash
- jq
- Claude Code
- hooks
GitHub (opens in a new tab)README (opens in a new tab)Changelog (opens in a new tab)
What he does
Claude Code enforces a five-hour usage limit and gives no warning before you reach it. Work stops mid-task, at a moment the limit chooses rather than you, and the only notice is the failure itself.
Kairos watches how much of the window you have spent, predicts what your next turn will cost, and refuses the prompt before that turn takes you through the wall. When he stops you, he asks what to do: hold until the window resets, send it anyway, or drop it. The prompt itself is kept, and /kairos go sends it after all, inside the same turn. You never have to type it again.

Nothing on your machine reports how much of the limit you have used, or where the limit is. Both are reconstructed from the transcripts Claude Code already writes.
Getting started
claude plugin marketplace add https://aranea-development.nl/plugins/marketplace.json
claude plugin install kairos@araneaHooks bind when a session starts, so start a new session before Kairos does anything. An already running session will not pick them up. He needs bash and jq, and if jq is missing he says so once and then does nothing.
If the install stops at ssh: connect to host github.com port 22, git is cloning the plugin from its GitHub repository over SSH and cannot reach GitHub that way on your machine. The message points at access rights, but the repository is public and the transport is what failed. One line switches git to HTTPS, then install again:
git config --global --add url."https://github.com/".insteadOf "git@github.com:"The README has the longer version, including how to undo it.
What he knows, and what he does not
The reconstruction is exact for consumption and inexact for the ceiling, so the ceiling is reported as a range with the evidence behind it. One recorded refusal gives a wide range, three give a tighter one.

An account Kairos has never seen hit a wall gets no range at all, and is never gated. He measures and reports and stays out of the way until he has observed a wall of that account's own.
That is deliberate. An earlier version seeded a plausible range from observed data. Reconstructing every window in that same history showed some of them reaching 16.5M billable tokens with no refusal at all, while the seed topped out at 5.7M. Those figures described one subscription and said nothing about the shape of the limit. A guessed ceiling would interrupt constantly on a larger plan while claiming to know something it had never observed.
One thing stays out of view whatever happens. Kairos reads only what Claude Code writes to ~/.claude/projects. Turns you spend on the same subscription in the desktop app or in the browser come out of that same five-hour window and leave nothing on your disk. If you work across more than one surface, read the report as a floor.
Two subscriptions
If you hold more than one Claude plan and switch between them, Kairos keeps them apart. Everything he records is partitioned by account, and a session follows the account that is actually paying even when you switch part way through. A Max 5x is told apart from a Max 20x, because their ceilings differ by roughly a factor of four and that distinction is the point.

Which account a turn is counted against comes from the transcript itself, not from whichever account is logged in when it is read. Claude Code writes the owner through the file line by line, so a session that changes hands part way through is split part way through. Subagent transcripts name no owner, and they are most of the files on disk, so a separate hook records which account was paying while each one was written.
A refusal is treated more strictly than a turn, because a refusal is the one thing that gives Kairos permission to interrupt you. On a machine with more than one subscription, a refusal that cannot be placed with certainty is still kept and reported, but never used to calibrate. Walls recorded before Kairos drew this distinction cannot be told apart after the fact, so on such a machine they are set aside once. kairos calibrate reads them out of the transcripts again, this time onto the account that hit them.
Kairos starts knowing nothing about a new account and gets better as he runs.
How the numbers are arrived at
Consumption counts input tokens, cache creation and output. Cache reads are excluded, and that came out of measurement. Weighting them at zero fits the recorded refusals, and including them at any weight makes the error four times worse. A meter that counted them would be wrong by roughly a factor of a hundred.
The five-hour windows are reconstructed from transcript timestamps as chained blocks floored to ten minutes. That model reproduces the reset times Claude's own refusals state, to the minute.
The next turn is predicted from the 75th percentile of recent turns, because the long tail is what actually puts a session through a limit. The gate compares against the optimistic edge of the range, so it stays quiet until even a generous ceiling is threatened. Kairos would sooner let a first wall happen than interrupt you wrongly for a month.
What he never does
He never writes your account's email address to disk. He never runs /login for you; he will say when another account looks clear and leave the switch to you. And he never blocks a prompt because something inside him broke. Every uncertain path lets the prompt through: being wrong about the budget is a nuisance, but a meter that stops your work because it broke is worse than no meter at all.
When something is wrong
Run /kairos. It prints what the plugin currently believes, and that is usually enough to tell the three quiet states apart, because from outside they look the same.
Nothing recorded for this account yet means no consumption has been read. Either jq is missing, or the hooks are not bound because the session was already running when you installed. Start a new session and look again.
No ceiling recorded yet is not a fault. It is the normal state for an account that has never hit a limit while Kairos was watching, and it stays that way until one happens. kairos calibrate rescans your whole transcript history for refusals it has not seen, which is worth doing once after installing.
A figure that looks wrong is worth checking against kairos accounts. If you use more than one plan, the number you are looking at belongs to whichever account is active now, and the other is listed beside it.
How it works
Five hooks, all of them harness-only, so none of this costs model context.
| Hook | Job |
|---|---|
SessionStart | Resolve the paying account, bind the session to it, bring the meter up to date, harvest any refusals |
UserPromptSubmit | Follow a mid-session account switch, predict the turn, block or let it through |
Stop | Record what the turn actually cost, and harvest a refusal if one just happened |
SubagentStop | Record which account paid for a subagent's transcript |
SessionEnd | Print the closing line |
Consumption is read incrementally. A cursor per transcript records how many bytes have been counted, so a refresh reads only what was appended since. Those cursors are kept in one place rather than per account, because a transcript's bytes should be counted once however many accounts you use. What comes out of them is partitioned by account, under ~/.claude/kairos/accounts/, which is where the recorded refusals and the turn history live too.
Platform notes
Tested on Linux, macOS and Windows on every push, and again under bash 3.2 on macOS, which is what macOS still ships as /bin/bash.
Two defects in this plugin's history were reachable only on Windows. Git converts line endings on checkout, and a carriage return riding on a numeric field yields not a wrong number but an arithmetic error. And replacing a file is not atomic there, so a concurrent writer can make one vanish between an existence check and a read. Both are handled, and both were found by CI.
Requirements
bash and jq. Linux, macOS and Windows. The test suite runs on all three.