Search the site

Press Enter to search, Esc to close.

Claude Code CLI and Antigravity CLI: Where Should a Rule Live?

For a while now, most of the commits going into this repository have carried an AI agent's signature. The agent that published the article you are reading is one of them. That much is already true of many teams.

What is different in this repository is this: the agents do not run from a single product. Anthropic's Claude Code CLI and Google's Antigravity CLI — each of them a command-line tool (CLI) that runs an agent in the terminal — sit side by side in the same repository. Whichever one it runs from, the agent reads the same rules, uses the same skills, calls the same tools and is stopped at the same places.

There are two reasons for that. The first is comparison: no review article can replace seeing two tools side by side on the same work, in the same repository, under the same rules. The second is to ask of the development process the question I ask when building an architecture: if this part were taken out tomorrow, what would be left?

The answer should live in the repository. The knowledge that lets an agent do its job well — how the system works, where things are, which job is done in which order — should belong to the repository, not to the product that runs the agent. Then the product and the model behind it both become replaceable parts: an outage, a price change or a change in contract terms does not stop development.

There is a second half to this, and it is what the article is really about: a rules file is a guide, not a constraint. The agent reads it and makes its own decision; whether that decision follows the rule can vary with the model, the version and the length of the context. That is acceptable for a rule meant to guide. It is not acceptable for a rule that must hold — such a rule has to live somewhere executable. This article describes where each rule in this repository lives.

The knowledge lives in the repository: rules and skills

Alongside the code that runs the site, the repository also carries how the site is to be developed. It has three layers.

The rules file loaded in every session. The repository map, the commands and the rules that hold whatever file is open. It is kept short on purpose — around two hundred and fifty lines — and leaves the detail to the layers below.

Path-scoped rules. Rules that matter only while one part of the tree is being edited live in separate files, and each file states at its top which paths it applies to. Claude Code CLI loads a rule when a file from that part of the tree is read: the stylesheet rule arrives when a stylesheet is opened, the content-model rule when the content tool is touched. Nine files, close to nine hundred lines.

Skills. A skill is a task-shaped workflow: publishing an article, uploading an image, taking a backup, promoting a deployment. There are fourteen in the repository, and each is a markdown file — which command runs in which order, where to stop and ask me, where to find a fact without reopening the source.

The reason for markdown is practical: both CLIs can read it, it needs no SDK, and I can review a change to it like code. What it solves is this: the agent starts every session without memory. Without the skill, it would rediscover where a field appears on the page by reopening the model and the template every time; the skill states it in a table.

The reasoning behind the decisions lives elsewhere, under docs/. Why a rule exists, which measurement it rests on, which configuration detail it depends on — none of that is loaded in every session; whoever needs it opens it. That split is what keeps the rules file short.

Tools: a decision made once

An agent can produce a script that writes to Firestore in a few seconds. That is exactly the problem: it produces a new script every time, and every script makes the decisions again. Which project does it connect to? Does it write the whole document, or only the changed field? Does it take a backup first? If the answers change from session to session, there is no system.

A tool is those decisions made once. When page-tool save saves an article, it backs up the previous version under history/, fills in the order number and the creation details, writes the sub contents, records the tag link in two places at once and refreshes the site search's record — whatever the admin panel's save service does. The two are deliberate mirrors: a document saved from the panel and one saved by the agent go down the same path.

My own tools in the repository are command-line tools too; there are seven today. Six of them touch data — content, configuration, media, the emulator's seed data, the llms.txt texts and backups — on Firestore, Storage and Auth. The seventh writes nothing and only reads a deployment's state from GitHub. All of them use the same shared library; environment selection, credentials and the diff output live there, in one place. Each of the six data tools has its own end-to-end test running against the emulator. Together they come to roughly 12 thousand lines of scripts today — 3,700 of them those tests.

The tools' dependencies also live in their own package, separate from the site's functions. An image-processing library that only the agent needs is never installed by the deployment pipeline.

How an article gets published

This article went live in this order:

Read this diagram as text

The agent first reads the skill and checks the article's tags; if a tag is missing, it creates that first. Then it runs the save command as a dry run and the tool only prints the diff. I approve the diff; the same command runs again with the write flag, and the CLI asks me about it separately. After that the sitemap and llms.txt jobs are queued, and last the tag report and the live address are verified.

  1. Skill SKILL.md
  2. Tags tag list
  3. Dry run no --yes
  4. Approval diff read
  5. Write --yes
  6. Jobs jobRunner
  7. Verify live address
  1. The agent reads the skill and works out the environment, the collection and the language.
  2. It checks the tags. This article's “AI” tag did not exist yet. The tool refuses to save an article linked to a tag that does not exist, because the tag page that link would appear on would open blank. The tag document is created first.
  3. Dry run. The agent runs the save command without --yes. The tool first checks the payload: the title, the content, the listing summary and the description are required. No job produces the description shown in search results; it is written by hand as one sentence of at most 170 characters. Then the tool prints the target path and the diff, and writes nothing.
  4. I read the diff and approve it. In every real environment, beta included.
  5. The same command, this time with --yes. The CLI asks me every time about any command that writes to a real environment with --yes; once I approve, the article, the automatic fields, the tag link and the site search's record are written. If an existing article is being updated, its previous version is backed up first.
  6. Jobs. The sitemap and llms.txt are each queued as a job document, again each with a dry run and an approval. llms.txt's full-text files do not read the article's own HTML but its markdown copy, kept in a separate document. So before the job, the agent prepares that text, and I read and approve it too. The tool waits for the job to finish, and a failed job comes back as the command's own error.
  7. Verification. The tag report and the live address.

This flow is the same in both CLIs: the skill is the same file, the tool is the same script, the approval sits at the same step. What differs is which CLI asks the question at step five.

Two CLIs, one repository

An agent running from either CLI had to read the same rules, use the same skills and be stopped at the same places. Those are two separate jobs, because knowledge and permission are carried differently. There is a third layer as well: the model.

The knowledge side: three symbolic links

Claude Code CLI reads its rules from CLAUDE.md and its skills from under .claude/skills/. Antigravity CLI looks for AGENTS.md and under .agents/. Instead of keeping copies, there are three symbolic links (symlinks): AGENTS.md → CLAUDE.md, .agents/skills → .claude/skills, .agents/rules → .claude/rules. All three are relative links committed to the repository; anyone who clones it fresh finds the rules and skills ready for both CLIs. The rules and the skills exist in a single copy, so there are no two copies to drift apart.

The permission side: two mirrored layers

Permissions cannot be solved that way, because the two CLIs have different permission models.

Claude Code CLI reads three lists from its settings file: allow, ask and deny. Today they hold 62, 57 and 3 entries. Antigravity CLI instead runs a script (a hook) before every tool call and waits for that script's decision. The same lists live there inside a bash script, as code.

The two layers mirror each other, and the mirror is kept by hand: a rule added to one is added to the other. What makes that easier is keeping the lists small. The allow side is deliberately broad — npm run * is a single entry — and the narrow lists hold the line: every command that overwrites the seed data, touches a live site, writes content to a real environment or changes the repository is on the ask or the deny list. Browser tools get the same split: the ones that read the page are allowed, the ones that click, type or run a script in the page are asked.

There is a shared part too: the two guard hooks that stop a test run while the emulator is down and check an edited markdown file on the spot exist in a single copy. The two CLIs send their data in different shapes, so the hooks read both.

The model side: product and model are separate choices

Antigravity CLI's model list includes, beside Google's Gemini family, Anthropic's Claude Sonnet and Opus models and OpenAI's open-weight GPT-OSS model: models from three separate companies in one tool. Claude Code CLI runs Claude models, but it can reach them through Amazon's, Google Cloud's and Microsoft's cloud platforms as well as through Anthropic's own service. So the product that runs the agent, the model behind it and the provider serving that model are three separate choices. Moving the repository from one CLI to the other does not require changing the model family, and changing the model family does not require changing the CLI. An outage or a change of terms at one provider does not stop development; at least one route stays open.

That separation is also what makes the comparison meaningful. Running the same model family in two CLIs, in the same repository, on the same task makes it easier to see how much of a difference comes from the model and how much from the tool running it.

I have verified end to end that this setup holds with both CLIs: they opened the same skill, ran the same tool against the same environment and stopped to ask at the same step. The behaviour described in this article belongs to Claude Code CLI 2.1.284 and Antigravity CLI 1.2.13; both tools are updated often, and the details can change from one version to the next.

Where each rule lives

Where a rule lives and what enforces it
Rule Where it lives Enforced by
Every command that touches data names its target environment; there is no defaultShared libraryTool — the command exits with an error
No save that would silently drop a fieldThe tool's save commandTool — for top-level fields
No article linked to a tag that does not existThe tool's save commandTool
No save without a title, content, summary and descriptionThe tool's save commandTool
Every write to storage states its reasonMedia tool (--note)Tool
Nothing is written without --yesThe tools' defaultTool — it only prints the diff
Writing to a real environment with --yesBoth permission layersCLI — I am asked every time
git add, commit, push, issue and PR writesBoth permission layersCLI — I am asked every time
npm run shell:prod-* (a Firebase functions shell on a live project), terraform destroyBoth permission layersCLI — denied
Code that fails lint or tests does not reach the remotepre-pushGit
The long form of a flag in commit commands (--message, not -m)Rules fileThe agent's judgement
Content is written in the site's voiceSkill textThe agent's judgement

The last two rows are there on purpose. The voice, when to stop and ask, which work is out of scope — these cannot be put into code; the tool I have for them is text, and text does the job. The distinction is this: what I write in text is an intention, and where I can undo the result, an intention is enough. Where I cannot undo it, the rule lives inside a tool, a script or a permission list.

Three decisions in the permission layer

Building this layer meant accounting for three things.

ask and force_ask are not the same. Antigravity CLI's permission prompt has an “Always Allow” option, and a plain ask returned by the script respects that cache: one earlier click is enough for later calls to go through unprompted. force_ask ignores the cache and asks every time. So every list that must always stop for a human — git and GitHub writes, tests that touch a live site, commands that write content to a real environment — uses force_ask. In the script, a plain ask is left in only two places: the catch-all at the very end, and the browser tools that act. Both are things Claude Code CLI also asks about only because they are not on its allow list; there, an “Always Allow” given to something harmless is meant to stick. Claude Code CLI needs no such split: a command on its ask list is asked every time, even after “don't ask again”.

For commands, network and MCP calls, the script returns a decision on every path. An empty answer counts as invalid and blocks the call with no reason given. Giving no answer at all is more dangerous: the decision falls to the CLI's terminal execution policy, and if that policy is always-proceed, the command runs unprompted. That is why the script ends in an explicit ask — everything the lists do not name is asked. In Claude Code CLI the default permission mode gives the same stance: apart from the built-in read-only commands, every command that is not on the allow list is asked. The two layers reach the same place (deny-by-default) by two different routes.

The script runs on the bash 3.2 that ships with macOS. A single construct from bash 4 is a syntax error there and silently disables the rest of the script. The script deliberately sticks to the forms 3.2 knows.

Limits

  • The mirror is kept by hand. No test checks that the two layers stay equal; their parity rests on the discipline of opening both files when a rule is added, and on comparing the two lists side by side at regular intervals.
  • The field guard only looks at the top level. seo.keywords disappearing while seo stays passes it. The real protection is fetching the live document and editing it in place — and that is the skill's rule, which is to say text.
  • The tools' tests run locally, not in CI. Deliberately: CI does not install their dependencies, because that would be a load downloaded on every deployment. pre-push runs those tests itself when a tool changes.
  • Permission lists match a command's text; they are not a security boundary (a sandbox). If the same program is invoked in an unusual form — from inside a shell wrapper, by another path — the list may not recognise it. In the default mode, both layers do not forbid the unrecognised form but ask about it; so what slips past does not run silently, it comes to me.
  • The layer stops the agent, not the user. The permissions committed to the repository define the default. Each developer can extend the allow list on their own machine with a local file, or start the CLI in a mode that hands the questions to a classifier (auto) or skips them altogether (bypass). That is deliberate: the aim is not to tie the hands of the person making the decision, but to keep the agent from deciding without them.

In closing

In this system, the tool that runs the agent and the model behind it were both built as replaceable parts: what they do is written down, where they stop is clear, and something else can take their place.

If you write code with AI, there are two things you can try. First: open your rules file and write next to each item who stops the command that breaks that rule. Among the items whose answer is “the agent's judgement”, the ones with consequences you cannot undo are waiting to be moved into a tool or a permission list. Second: swap the tool that runs your agent for another one for a day. However much keeps working is how much of your knowledge lives in the repository.

If you would like to go through that inventory together for your own repository, write to us through the contact page; your rules file and your permission settings are enough to start.

This article is the third in a series. Why the system was built and the bill for eight years: GCP Cost: The 8-Year Bill for One Decision. How it works: The Architecture You're Visiting: How This Site Runs on GCP.


This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License .

Please review our Terms of Use for details on quotation and data mining restrictions.


About Us

In the software industry since 2008, we turn your Utopical ideas into reality with modern AI, robust cloud infrastructures, and custom software.

Our Projects

  • Sevgiyle Büyüyen Fidanlar sevgiyle.org — a personal publication of stories, dreams and writing drawn from nature and everyday life.
  • Super Murat supermurat.com — “Let me make you smile”: a long-running personal blog of notes and humour.
  • Jeux d’Art app.jeuxdart.com — digital and printed artworks and events in one platform.

Our Open Source Projects

  • super-firebase-angular A full-stack sample application that brings Firebase and Angular together · archived
  • hamsi-manager A desktop file manager written for bulk file operations (Python) · archived
  • super-reader A personal RSS reader you can deploy on your own Firebase project · archived

Follow Us