Late one afternoon a request I had just sent stopped coming back. The usage indicator on the right of the status line had changed color, and I was standing right at the edge where the baseline quota runs out. My hands were idle for maybe two minutes, and all I could think about was whether I should keep going.
Whether I could keep going was not up to my mood that afternoon. It had already been decided by a single setting — one I only learned about after the indicator changed color.
One setting decides whether you wait or you pay
The official Plans page describes a baseline quota on every plan. On Google AI Pro and Ultra it refreshes every five hours until the weekly rate limit is reached; below those plans, the refresh is weekly. The same page notes that the limits correlate with how much work the agent actually did, which differs from prompt to prompt. So counting "how many requests do I have left" is not a thing you can do.
What happens past the baseline is a separate question: whether you keep going on purchased AI credits. That is governed by the "AI Credit Overages" setting, which has exactly two values — Never and Always. Never waits for the refresh. Always spends credits automatically and switches back to the baseline on its own once the refresh lands. In the CLI, the same lever is useG1Credits in ~/.gemini/antigravity-cli/settings.json, or the Use G1 Credits field in the /config and /settings panel.
There are three places to read your remaining allowance: the indicator at the right of the status line, the balance and consumption breakdown behind /credits, and the per-model view behind /usage (aliased as /quota). If you would rather start from the money side, I wrote about where each plan breaks even in Antigravity Pricing and Usage Limits — Free Tier, Pro, or AI Ultra?.
What Always cost me during one particular week
As an indie developer I keep a handful of apps running, and I batch the unglamorous work — store copy drafts, maintenance on the Lab sites — into the hours when nobody is awake. My setting sat on Always. Not stopping felt safer.
That week, a nightly run crossed the baseline. The balance in the morning was visibly lower. What stung was not the amount. It was that I could not remember what it had been spent on. Nothing in that batch was urgent; I had paid money for drafts that would have gone through for free if I had simply let them wait.
One line came out of it. Money for the work someone is waiting on; time for the work nobody is waiting on. Once the direction was settled, I stopped touching the setting so often.
Three boxes first, then the setting
I tried finer categories at one point and only made the decision harder. These days I drop work into three boxes and stop there.
| Box | What you spend | Direction at the limit | How a wrong call shows up |
|---|---|---|---|
| Someone is waiting (client deadline, live defect) | Money | Always — keep going | The balance drops faster than expected |
| Nobody is waiting (nightly batches, draft generation) | Time | Never — wait for the refresh | You find it unfinished in the morning |
| Exploration and learning loops | Neither | Never, on a lighter model | You drift toward the weekly ceiling unnoticed |
The third box was the one I kept overlooking. Each exploratory round trip is cheap, so the count climbs, and the count is what pushes you into the weekly ceiling. With Never in place, your hands stop when the ceiling is near. Whether that stop reads as friction or as a signal is exactly what determines how you feel about this setting.
A pause in front of the runs nobody watches
I am not there for the nightly jobs. Nobody is around to notice the indicator changing color, so setting Never is not enough by itself — something has to check the remaining allowance before the job starts.
The status line script turns out to be the place where that number is machine-readable. Every time the agent state changes, the CLI runs your script and pipes a state JSON payload to its stdin. Inside that payload, quota carries remaining_fraction, reset_time, and reset_in_seconds per bucket. I keep exactly one snapshot of it on disk.
#!/usr/bin/env bash
# ~/.gemini/antigravity-cli/quota-snapshot.sh
# Print one line for the status line, and keep a single snapshot on disk (no history)
set -u
SNAP="${HOME}/.gemini/antigravity-cli/quota_snapshot.json"
PAYLOAD="$(cat)" # the state JSON arrives on stdin whenever the agent state changes
printf '%s' "${PAYLOAD}" \
| jq -c --arg at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{captured_at: $at, tier: (.plan_tier // "unknown"), quota: (.quota // {})}' \
> "${SNAP}.tmp" 2>/dev/null \
&& mv "${SNAP}.tmp" "${SNAP}"
printf '%s' "${PAYLOAD}" | jq -r '
(.quota // {}) | to_entries
| if length == 0 then "quota -"
else map("\(.key) \(((.value.remaining_fraction // 0) * 100) | floor)%") | join(" ")
end'Wire it up through the statusLine block, and set stack_with_default so the built-in line survives. Replacing the default display with your snapshot logger makes your everyday work harder to see.
{
"statusLine": {
"type": "command",
"command": "~/.gemini/antigravity-cli/quota-snapshot.sh",
"stack_with_default": true
},
"useG1Credits": false
}The write goes to a temporary file and then through mv because this script runs again and again as the state changes. Write in place and the reader eventually picks up a half-written JSON file. Accumulating consumption into a ledger is a different job, and I kept it in its own piece — Three things I had to fix before my status line could tell me what a session cost. Here there is no ledger; there is one snapshot.
The pause itself is a small gate that reads that snapshot and answers one question: run now, or wait for the refresh.
#!/usr/bin/env python3
"""A pause in front of unattended runs.
exit 0 ... fine to run right now
exit 75 ... defer (the caller skips this round)
When the remaining allowance cannot be read, it falls to "defer".
"""
import json
import os
import sys
import time
SNAP = os.path.expanduser("~/.gemini/antigravity-cli/quota_snapshot.json")
MIN_REMAINING = 0.25 # below this share of the baseline, unattended runs are skipped
MAX_AGE_SEC = 6 * 3600 # the snapshot goes stale; it only updates while the CLI is open
DEFER = 75
def load_snapshot(path):
try:
age = time.time() - os.path.getmtime(path)
with open(path, encoding="utf-8") as fp:
return json.load(fp), age
except (OSError, ValueError):
return None, None
def main():
snap, age = load_snapshot(SNAP)
if snap is None:
print("defer: could not read the snapshot")
return DEFER
if age > MAX_AGE_SEC:
print(f"defer: snapshot is too old ({age / 3600:.1f} hours)")
return DEFER
buckets = snap.get("quota") or {}
if not buckets:
print("defer: quota was empty (possibly before the first call)")
return DEFER
name, worst = min(buckets.items(), key=lambda kv: kv[1].get("remaining_fraction", 0.0))
remaining = worst.get("remaining_fraction", 0.0)
wait_min = int(worst.get("reset_in_seconds", 0) // 60)
if remaining < MIN_REMAINING:
print(f"defer: {name} is at {remaining:.0%} (refresh in about {wait_min} min)")
return DEFER
print(f"go: {name} is at {remaining:.0%}")
return 0
if __name__ == "__main__":
sys.exit(main())On my machine it prints one of these:
$ python3 ~/bin/quota_preflight.py
go: gemini-weekly is at 93%
$ python3 ~/bin/quota_preflight.py
defer: gemini-weekly is at 11% (refresh in about 74 min)The caller stays quiet if you treat a deferral as a normal outcome rather than a failure.
# called from the nightly job
python3 ~/bin/quota_preflight.py || exit 0
agy -p "$(cat ~/prompts/nightly_draft.md)"The decision runs on the thinnest bucket rather than an average, because each model carries its own allowance and one empty bucket is enough to stop the job. Average the buckets and the one that will actually stop you disappears into the mean.
When the number cannot be read, choose not to run
There is one plain weakness here. The snapshot only updates while the CLI is open. If you have not opened it since the previous evening, the gate answers "go" from a stale number.
A gate that decides on stale data is worse than no gate at all. That is why the snapshot has an expiry and why anything unclear falls to defer. An empty quota gets the same treatment: the field can be absent before the first call, and reading empty as "zero left" rather than "unknown" quietly changes the behavior.
One more thing worth keeping separate. Choosing Never does not move the weekly ceiling. The setting only decides whether you cross the limit with money. If you want to hit the ceiling less often, that is a conversation about how you ask and how much context you hand over.
What to do next
Open /usage and look at the per-model remaining allowance and the time to refresh. Then set Overages to Never and live with it for one week. Counting the moments where you were stopped tells you, without much analysis, which box your work belongs in.
My own threshold for unattended runs moved up again last week, so I am still adjusting. If any of this saves you the morning I had, I am glad.