◉ANTIGRAVITY LABJP
Articles/Antigravity Basics
◉ Antigravity Basics/2026-09-26Beginner

I Counted My Rules Against the 20,000-Token Budget in Antigravity 2.17.0 Before Upgrading

Antigravity 2.17.0 gives rules their own 20,000-token budget. My six operating rule files came to 28,613 tokens, so the two largest would be demoted to pointers. Here is a small script to count yours first, and how I chose what to demote myself.

Antigravity 2.015rules2AGENTS.md14context8token budgetmodel_decision

I was reading the 2.17.0 changelog late one evening when a single line stopped me: "Rules no longer crowd out everything else." Read the other way round, that sentence says rules had been crowding out everything else until now.

As an indie developer I run four Lab sites, and their operating rules have been split and trimmed more than once, yet the total kept growing. It was hard not to picture that thick rule set quietly pushing my skills and MCP tools out of the agent's context without my noticing. My stomach sank a little.

So before installing the update, I decided to count. The result was 8,613 tokens over the budget.

The two limits that 2.17.0 settles

The rules documentation describes two limits, and they use different units, which matters more than it sounds.

LimitHow it is measuredWhat happens when you exceed it
24,000 bytes per fileByte size after expanding @[label](path) includesThe tail of the file is truncated
20,000 tokens in totalAll global and always_on rules combinedThe largest files are demoted, one by one, to a pointer of path plus description

That 20,000-token budget is separate from the customization budget that skills, subagents, and MCP tools draw from. Before 2.17.0 they all shared one pool, so a heavy rule set meant a lighter everything else.

One more detail is worth holding onto. A model_decision rule enters the prompt as a pointer from the start: only its path and description are shown up front, and the agent opens the full text when it judges the task relevant. From the agent's point of view, a rule you demote yourself and a rule the budget demotes for you look almost identical.

A small tool to count what you have

I wanted three answers: whether any file exceeds 24,000 bytes after includes are expanded, whether the always_on total fits under 20,000 tokens, and, if not, which files would fall to pointers first.

Antigravity will tell you all of this, but only after you upgrade and open the workspace. I wanted to know beforehand, so I wrote a short Python script that reads a workspace's rule files and runs the estimate.

#!/usr/bin/env python3
"""Estimate Antigravity's rule budgets for a workspace before upgrading.
- warn on files over 24,000 bytes (after expanding @[label](path) includes)
- compare the always_on total (plus frontmatter-less AGENTS.md / GEMINI.md) with 20,000 tokens
- if over budget, simulate demotion largest-first and show what stays inline
Token counts use tiktoken cl100k_base as a proxy; Gemini's real count will differ a little.
"""
import re, sys, pathlib
import tiktoken
 
FILE_LIMIT_BYTES = 24_000
RULES_BUDGET_TOKENS = 20_000
VALID_TRIGGERS = {"always_on", "model_decision", "glob", "manual"}
enc = tiktoken.get_encoding("cl100k_base")
INCLUDE = re.compile(r"@\[[^\]]*\]\(([^)]+)\)")
 
def expand_includes(text: str, base: pathlib.Path) -> str:
    def repl(m):
        target = pathlib.Path(m.group(1)).expanduser()
        if not target.is_absolute():
            target = (base / target).resolve()
        try:
            body = target.read_text(encoding="utf-8")
        except OSError:
            return m.group(0)  # leave the reference as-is when the file is missing
        return re.sub(r"\A---\n.*?\n---\n", "", body, flags=re.S)  # drop the included file's frontmatter
    return INCLUDE.sub(repl, text)
 
def read_rule(path: pathlib.Path):
    raw = path.read_text(encoding="utf-8")
    trigger, desc = None, ""
    m = re.match(r"\A---\n(.*?)\n---\n", raw, flags=re.S)
    if m:
        fm = m.group(1)
        t = re.search(r"^trigger:\s*(\S+)", fm, flags=re.M)
        d = re.search(r'^description:\s*"?(.*?)"?\s*$', fm, flags=re.M)
        trigger = t.group(1) if t else "INVALID"
        if trigger not in VALID_TRIGGERS:
            trigger = "INVALID"  # alwaysOn / modelDecision etc. are silently discarded
        desc = d.group(1) if d else ""
    elif path.name in ("AGENTS.md", "GEMINI.md"):
        trigger = "always_on"  # always active without frontmatter
    else:
        trigger = "INVALID"  # a rules/ file without frontmatter is silently discarded
    body = expand_includes(raw, path.parent)
    return {"path": path, "trigger": trigger, "desc": desc,
            "bytes": len(body.encode("utf-8")), "tokens": len(enc.encode(body))}
 
def main(root: str):
    root = pathlib.Path(root)
    files = [p for p in (root / "AGENTS.md", root / "GEMINI.md") if p.exists()]
    files += sorted((root / ".agents" / "rules").glob("*.md"))
    rules = [read_rule(p) for p in files]
    active = [r for r in rules if r["trigger"] == "always_on"]
    total = sum(r["tokens"] for r in active)
    print(f"{'file':<28}{'trigger':<16}{'bytes':>8}{'tokens':>8}  note")
    for r in rules:
        note = []
        if r["bytes"] > FILE_LIMIT_BYTES: note.append("OVER 24,000 B -> truncated")
        if r["trigger"] == "INVALID": note.append("discarded (no/invalid frontmatter)")
        print(f"{r['path'].name:<28}{r['trigger']:<16}{r['bytes']:>8}{r['tokens']:>8}  {' / '.join(note)}")
    print(f"\nalways_on total: {total:,} tokens / budget {RULES_BUDGET_TOKENS:,}")
    if total <= RULES_BUDGET_TOKENS:
        print("OK: everything stays inline"); return
    demoted, remaining = [], total
    for r in sorted(active, key=lambda r: r["tokens"], reverse=True):
        if remaining <= RULES_BUDGET_TOKENS: break
        demoted.append(r); remaining -= r["tokens"]
    print(f"over by {total - RULES_BUDGET_TOKENS:,}; demoted to pointers (largest first):")
    for r in demoted:
        print(f"  - {r['path'].relative_to(root)}: {r['desc'] or '(no description)'}")
    print(f"inline after demotion: {remaining:,} tokens")
 
if __name__ == "__main__":
    main(sys.argv[1] if len(sys.argv) > 1 else ".")

Run pip install tiktoken once, then python3 rules_budget_probe.py . from the workspace root. The output is one line per rule file with its trigger, byte size, and token estimate, followed by the always_on total and, when you are over, the list of files that would become pointers. Global rules under ~/.gemini/config/rules/ share the same budget, so if you keep any there, add that directory to the list the script scans.

Two choices in the script deserve a word. Include expansion happens before the size check because the documentation states that the 24,000-byte limit is measured after @[label](path) includes are expanded. And trigger is checked against the four valid values because a misspelled trigger does not raise an error; the rule is simply dropped, which I ran into myself a few minutes later.

The token count is a cl100k_base approximation, so it will not match Gemini's tokenizer exactly. It is not the tool for a decision that hinges on a few hundred tokens, but it answered the question I had: are we over, and by roughly how much.

What the count showed

My Lab operating rules consist of an AGENTS.md with the essentials, plus five companion files: a reference table, shared procedures, site-specific values, the writing voice rules, and a checklist. Here is what the script reported with all of them set to always_on.

FileRoleBytesTokens (approx.)
AGENTS.mdOperating essentials15,3925,685
content-core.mdShared procedures and checks21,9937,988
reference.mdReference tables19,9447,114
voice.mdWriting voice rules12,2804,593
site.mdSite-specific values5,1661,830
checklist.mdWriting checklist3,6231,403
always_on total28,613

The total came to 28,613 tokens, 8,613 over the budget. In the simulation, content-core.md (7,988) and reference.md (7,114) would be demoted largest-first, leaving 13,511 tokens of rules inline.

That is where I paused. Of the two files that would be demoted, content-core.md was the one I most wanted the agent to have in front of it at all times. Meanwhile the 3,623-byte checklist would stay inline without my lifting a finger.

The budget does not weigh importance. It weighs size. The documents I had written most thoroughly because they mattered most were, for exactly that reason, the biggest, and the biggest are what fall first. The reason I made them thick and the reason they get demoted turned out to be the same reason.

Three things that ran against my intuition

Running the estimate a few times surfaced three things I would not have learned from the documentation alone.

The first is the largest-first order itself. If you need to shrink something, trimming your most important document is what moves the needle most.

The second is that you can be comfortably under the token budget and still be truncated by bytes. When I appended a single line, @[reference](./reference.md), to the end of AGENTS.md to pull the reference table in, the expanded file came to 35,231 bytes, past the 24,000 limit and into truncation territory. Its token count was 12,769, well inside the 20,000 budget. Includes are convenient, but everything they pull in counts toward that file's byte size.

The third is that a test file with trigger: alwaysOn vanished from the listing without a warning. The documentation says such rules are silently discarded, and it means it. My own script initially printed alwaysOn as if it were fine; only after I added the validation did it report INVALID. When a rule seems not to be taking effect, the first suspect is spelling, much like the cases in Four Reasons Your .antigravityignore Rules Are Not Taking Effect.

Demote it yourself before the budget does

Having seen the numbers, I switched the two files to model_decision myself. All that took was editing the frontmatter of content-core.md and reference.md and rewriting each description into one sentence that says when the file should be opened.

Counting again, the always_on total dropped to 13,511 tokens, with 6,489 to spare. What the agent sees is nearly the same as if the budget had demoted them. The one difference is that I chose which files went, rather than the budget choosing by size.

Decide the demotion order yourself before the budget decides it for you. That is the one line I now hold to every time the rule set grows.

Writing those descriptions was the part that took real thought. A description like "shared procedures" tells the agent nothing about when to open the file; "open before running the content checks or pushing to the repository" does. The pointer is only as useful as the sentence next to it.

I also set a rule for what stays. Short prohibitions and the writing voice remain always_on; long procedures and reference tables get a careful description and move to model_decision. Procedures have a clear moment when they are needed, and the agent opening them at that moment is enough.

The same 2.17.0 release moved per-repository configuration to .gemini/config.json and stopped reading the old .agents/settings.json. If you keep a watcher like the one in A Small Guard Script That Catches an Unattended Agent Rewriting Its Own Settings, this is a good moment to point it at the new location as well.

Count one workspace first

Before you upgrade, I'd suggest running the script once from your workspace root. If the total is over 20,000, rewrite the description of the single largest file and move it to model_decision. That alone turns a demotion the budget would have picked into one you picked.

I may be missing something, but demoting the document I cared about most turned out to be fine: the agent opens it when the task calls for it, and it has not once needed me to remind it.

Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◉ Antigravity2026-03-15
Antigravity Context Design Guide— Make AI Actually Understand Your Project with AGENTS.md and Knowledge Items
Learn how to design project context so Antigravity performs at its best. Covers writing AGENTS.md, building Knowledge Items, sharing context across teams, and improving suggestion quality incrementally.
◉ Antigravity2026-07-04
When a Stable Line and a Feature Line Ship Together, Which Build Should a Solo Operator Ride?
By shipping a 2.0 (stable) line and a 2.1 (feature) line in parallel, Antigravity now asks solo developers to decide which one to ride. Here is a design for selecting and switching builds without breaking your automation.
◉ Antigravity2026-07-03
Riding Two Release Lines — Splitting Antigravity's 2.0 and 2.1 Tracks Between Unattended and Interactive Work
On July 1, Antigravity published 2.0-line and 2.1-line builds side by side. Here is a version policy that keeps unattended automation on the stable line and interactive work on the feature line, with a declaration file and drift detection.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links