Gravity CLI
The command-line tool for building, testing and publishing AI agents on Gravity.
Write ordinary LangGraph in Python, declare what your agent may touch in one manifest,
and run it against real tools with exactly those permissions enforced.
Install · Quick start · Commands · Configuration · Manifest · Troubleshooting
gravity run on an agent that called a tool it never declared. The gateway refused that one call, and the run carried on:
inbox-digest@1.0.0 run grt_…4e7b · 2 tools declared
● read 0.9s
✓ gmail.read 3 found 0.8s
● digest 2.3s
◆ openai/gpt-6-luna 2.1s
✗ slack.send refused — not in permissions.tools 0.0s
╭─ result ───────────────────────────────────────────────────╮
│ unread 3 │
│ headline Two invoices and a meeting request need replies. │
╰────────────────────────────────────────────────────────────╯
✓ Done in 3.3s
pipx install gravity-cli
gravity init my-agent
Status.
login,push,runand the tool catalog work out of the box against Gravity's hosted services.gravity loginissues you the gateway token thatrunneeds; nothing goes in.env.
Contents
- Declare it once, enforced on every call
- What is in the box
- Five things to know before you start
- Installation
- Quick start
- How it fits together
- Command reference
- Configuration
- Authentication
- The manifest
- An agent project
- Running a local gateway
- Troubleshooting
- Limitations
- Development
- Documentation
Declare it once, enforced on every call
There is almost no SDK to learn. Your agent is plain LangGraph that reads three environment variables; the one Gravity package, gravity, is preinstalled on the platform and is only needed to load approval-gated tools. Everything Gravity enforces comes from manifest.yaml:
You declare in manifest.yaml |
What Gravity does with it |
|---|---|
inputs |
Renders the form a consumer fills in, and passes the answers in state["inputs"] |
permissions.tools |
Issues a run token that allows exactly those tools. Any other call is refused by the gateway |
approvals.required_for |
Pauses those calls until a person approves them |
triggers.schedule |
Runs the agent on that cron schedule |
state |
Keeps a small memory between runs |
The same rules apply on your laptop and when the agent runs hosted, because both go through the same gateway.
What is in the box
| Commands | 21, grouped into build an agent, publish, your agents, account, help and Gravity CLI |
| Starter templates | 4: minimal, structured, deepagent, chat |
| Tool catalog | 1,405 tools across 12 apps (Gmail, Slack, Linear, GitHub, Calendar, Drive, Sheets, LinkedIn, Jira, Confluence, Notion, WhatsApp), plus web search, Firecrawl and two built-ins; 31 of them curated |
| Test suite | 543 tests (285 CLI, 258 schema), run on Ubuntu and Windows with Python 3.12 and 3.13 |
| Runtime contract | 3 environment variables; an optional import gravity only for approval-gated tools |
Five things to know before you start
- Inputs arrive in
state["inputs"]; whatever you put instate["result"]is what the consumer gets. graphis anasync deffactory. Read environment variables inside it, never at import time.- A tool that is not in
permissions.toolsis refused, whatever your code does. - A tool listed in
approvals.required_forwaits for a yes in the terminal before it runs. - The platform runs Python 3.12.
gravity initwrites that, andgravity validatefails on anything else.
Installation
Requires Python 3.12 or newer. Agents use uv for their own environments.
pipx install gravity-cli # recommended: gravity gets its own environment
uv tool install gravity-cli # the same, with uv
pip install gravity-cli # into the current environment
gravity then works from any folder. To upgrade: pipx upgrade gravity-cli or uv tool upgrade gravity-cli.
If pip answers No matching distribution found for gravity-cli, your Python is older than 3.12 (check with python --version; conda's base often is). uv tool install gravity-cli avoids the problem, because uv fetches a suitable Python by itself. With pip, pick a newer interpreter: py -3.12 -m pip install gravity-cli on Windows, or python3.12 -m pip install gravity-cli elsewhere.
The first time you run gravity, it shows a short welcome and offers to create your first agent.
Quick start
1. Create and check an agent. No account or setup needed:
gravity init my-agent
cd my-agent
uv venv && uv pip install -r requirements.txt
gravity validate
2. Run it. Needs a gateway and your dev token, see Configuration:
gravity run --input query="hello"
3. Publish it. Needs a Gravity account:
gravity build
gravity login
gravity push
How it fits together
your machine Gravity
──────────── ───────
gravity init / validate / build (nothing leaves your machine)
gravity run ────── dev token ──────────────────→ gateway ──→ tools (MCP) and models (/v1)
└─ starts your agent with a run token ──────→ gateway only the declared tools are allowed
gravity login / push / tools / agents ─────────→ control plane ──→ builds and hosted runs
| Service | What it does | Used by |
|---|---|---|
| Gateway | Serves tools over MCP (/mcp) and models over an OpenAI-compatible API (/v1). Lists its tools at /tools and your connected accounts at /connections. Issues run tokens and refuses any tool a run was not granted. |
run, schedule, adapt, tools, connect, and your agent |
| Control plane | Accounts, pushed agents, builds and hosted runs. | login, logout, whoami, push, agents, runs, logs |
On gravity run, the CLI trades your dev token for a run token scoped to the manifest's tools and starts the agent with only that token. Your own tokens are removed from the agent's environment, so agent code can never act as you.
Command reference
Run gravity help for this list in the terminal, and add -h to any command for its options.
| Group | Command | What it does |
|---|---|---|
| Build an agent | gravity init |
Create a new agent from a template |
gravity validate |
Check your agent for mistakes before it runs | |
gravity run |
Run your agent locally against real tools | |
gravity dev |
Open a local dashboard to run and watch your agent | |
gravity schedule |
Run your agent on its schedule, locally | |
gravity adapt |
Write a new slot adapter with AI, kept only if its tests pass | |
| Publish | gravity build |
Package your agent into build/agent.zip |
gravity push |
Publish your built agent to Gravity | |
| Your agents | gravity agents |
List your published agents and their review status |
gravity runs |
Show recent runs of one agent | |
gravity logs |
Show the logs of one run | |
gravity tools |
Browse the tools your agent can use | |
| Account | gravity login |
Sign in to Gravity in your browser |
gravity logout |
Sign out | |
gravity whoami |
Show who you are signed in as | |
gravity connect |
Connect an account your agents act on (Gmail, Slack...) | |
| Help | gravity guide |
Read the builder guide: how to build a good agent |
gravity help |
Show the list of commands | |
| Gravity CLI | gravity docs |
Write Gravity.agent.md for the coding agent in this folder |
gravity update |
Update to the latest release and see what is new | |
gravity changelog |
What changed in each release |
gravity --api-url URL <command> saves a different control-plane address to ~/.gravity/config.json; it must start with http:// or https://. The flag applies to the command it is given with; on later commands a GRAVITY_API_URL in ~/.gravity/.env or the shell still wins over config.json, and the CLI says so when that is the case. gravity --version (or -V) prints the installed version. Wherever a command takes [path], it is the agent's folder and defaults to the current one.
gravity init
Create a new agent that already runs: a filled-in manifest, pinned dependencies and a working src/agent.py.
gravity init [name] [--template TEMPLATE] [--here]
| Argument or option | Description | Default |
|---|---|---|
name |
Name of the agent, and of the folder created. Lowercase letters, digits and single hyphens. | required, unless --here |
--template |
Starting point: minimal, structured, deepagent or chat |
minimal |
--here |
Add only a manifest.yaml to the current folder, for an existing project. The name comes from the folder. |
off |
| Template | What you get |
|---|---|
minimal |
One file. A LangGraph agent that answers a query with the declared tools |
structured |
The same agent split into state, prompts, nodes and gateway modules, for larger projects |
deepagent |
An agent built on the deepagents harness, with file access denied by default |
chat |
A conversational agent that keeps the thread between turns |
gravity init inbox-digest
gravity init support-bot --template chat
gravity init --here
It refuses to overwrite an existing folder or manifest.yaml.
gravity validate
Check an agent on your machine, with no Gravity service involved. It reports every problem at once, each with a suggested fix.
gravity validate [path]
What it checks:
manifest.yamlis valid, field by field- the entrypoint file exists and defines the object the manifest names
requirements.txtexists, and compiles intorequirements.lockfor the platform image (ARM64 Linux) and the manifest's Python version- every adapter a slot declares has a file, and every adapter file is declared
- every tool in the manifest exists in the catalog, the same list the platform's build uses, so a typo fails here instead of after a push
- every imported write tool is under
approvals.required_for - your code never names a write tool the manifest does not grant
- a warning, never a failure, for read tools your code names but the manifest does not declare
graphis anasync def(or a compiled graph), not a plaindefor a bare value; a name bound by an import counts- nothing under
src/readsos.environor callsasyncio.runat module level, and no import is relative - a schedule runs at most every 5 minutes and restricts the day of the month or the weekday, not both
gravity validate
gravity validate ./my-agent
Exits with code 1 if anything is wrong.
gravity run
Run the agent locally through the gateway and watch it live: each step, model call and tool call, then a result card.
gravity run [path] [--input KEY=VALUE]... [--reply TEXT] [--llm-url URL]
| Option | Description |
|---|---|
--input KEY=VALUE |
An input, repeatable. KEY=@file.txt reads the value from a file. Inputs with a default in the manifest can be left out. |
--reply TEXT |
Continue the last conversation of a chat agent |
--llm-url URL |
Model endpoint to use instead of GRAVITY_LLM_URL |
gravity run --input query="summarise my unread email"
gravity run --input transcript=@call.txt --input tone=formal
gravity run --reply "make it shorter"
- Requires a gateway token: the one
gravity loginstores, orGRAVITY_DEV_TOKEN. - A call to a tool in
approvals.required_forpauses, shows what it would do, and waits foryorn. With no terminal to ask in, it is rejected. - Exits with code 0 when the run finishes and 1 when it fails.
- Leaves a record in
.gravity/runs/<id>/(run.jsonandevents.jsonl), which is whatgravity devreads. One run is live per folder at a time, because runs share the folder's memory, store and thread files.
gravity dev
Open a local dashboard for the agent in this folder: a form built from the manifest's inputs, a live timeline of each step, model call and tool call, approval buttons for held calls, the result, and a history of runs.
gravity dev [path] [--port 4747] [--no-open] [--llm-url URL]
| Option | Description |
|---|---|
--port N |
Port to listen on (default 4747). If it is taken, a free one is used and printed. |
--no-open |
Print the address without opening a browser (SSH, WSL, CI) |
--llm-url URL |
Model endpoint to use instead of GRAVITY_LLM_URL |
- A run from
gravity runin the same folder shows up in the dashboard live, and a run started in the dashboard is the same run (same engine, same memory, store and thread files), sogravity run --replycontinues it. - It listens on
127.0.0.1only, behind a random token in the printed address, for as long as the command runs. Ctrl+C stops it and any run it started. Nothing keeps running in the background. - Connected accounts: it shows the accounts your tools need and a Connect button for each one that is missing.
- Approvals: a call held for approval can be decided in the terminal or in the dashboard; whichever answers first wins.
- Cancel a run from the dashboard only if the dashboard started it; stop a terminal run with Ctrl+C in that terminal.
- Results show as text. Word, PowerPoint and CSV files are produced by Gravity after you push. A file input is read as text here.
- Same requirements as
gravity run: a gateway token fromgravity login, orGRAVITY_DEV_TOKEN.
gravity schedule
Run the agent on its triggers.schedule, the way the platform will, until you press Ctrl+C. Ticks are timed in Asia/Kolkata, as the hosted schedules are. Each tick is an ordinary run with the same inputs; a missing required input is refused before the first tick.
gravity schedule [path] [--input KEY=VALUE]... [--cron EXPR] [--now]
| Option | Description |
|---|---|
--input KEY=VALUE |
An input, repeatable. The same inputs are used for every run. |
--cron EXPR |
Use this 5-field cron schedule instead of the manifest's, if the manifest allows changes |
--now |
Run once right away, then keep to the schedule |
gravity schedule --input send_to=me@example.com
gravity schedule --cron "*/30 * * * *" --now
Nobody is at the terminal during a scheduled run, so calls that need approval are rejected and the agent takes its fallback. A failed run does not stop the schedule.
gravity adapt
Have a coding agent write one new adapter for a slot. The file is kept only if the slot's conformance tests pass; otherwise the file and the manifest are put back as they were.
gravity adapt <slot> <name> --tools TOOLS [--hint TEXT] [--model MODEL] [--path PATH]
| Argument or option | Description | Default |
|---|---|---|
slot |
The slot to extend, for example deliver |
required |
name |
Name of the new adapter, for example slack |
required |
--tools |
Tools the adapter may call, comma-separated. This is what it is granted. | required |
--hint |
Anything the coding agent should know, for example 'post to #leads' |
none |
--model |
Model that writes the adapter | anthropic/claude-sonnet-5 |
--path |
Agent folder | current folder |
gravity adapt deliver slack --tools slack.send,slack.list_channels --hint "post to #leads"
Requires tests/test_<slot>_adapters.py in the project and a gateway token (gravity login, or GRAVITY_DEV_TOKEN). It makes up to 3 attempts, feeding each test failure back to the model.
gravity build
Validate the agent, then bundle it into build/agent.zip with its hashes in build/build.json. Nothing leaves your machine, so you can inspect the zip before publishing.
gravity build [path]
The bundle holds manifest.yaml, requirements.txt, requirements.lock, src/, tests/ and tools_needed.yaml when present; dotfiles under src/ and tests/ are left out. It refuses to build without requirements.lock (run gravity validate first). The same source always produces the same bytes.
gravity build
gravity push
Upload exactly what gravity build made, then wait while Gravity builds it.
gravity push [path]
- Requires
gravity login, orGRAVITY_API_TOKENfor development. - Refuses if there is no build, or if the source changed since the build. Run
gravity buildagain. - A published version can never be replaced. Raise
versionin the manifest to publish again. - Press Ctrl+C to stop waiting; the build carries on, and
gravity agentsshows its status. - The bundle may be at most 30 MB; a larger
agent.zipis refused before upload. Remove large files fromsrc/ortests/and build again. - The manifest travels in a request header, so
manifest.yamlmust stay under 12 KB once encoded; a larger one is refused before upload. Keep descriptions and help text short. - Re-pushing a version whose build failed, without changing the source, is refused with the failure reason: fix it and raise
versionin the manifest.
gravity build && gravity push
gravity agents
List your published agents with their version, status, review and last update. Under the table it prints what the Gravity team told you: the reviewer's notes on an approved or rejected version, and the reason if an agent was turned off. A rejected version needs a fix and a new version: in manifest.yaml before you push again. A failed build prints its reason under the table.
gravity agents
gravity runs
Show recent hosted runs of one agent, with status, start time and cost.
gravity runs <agent_name>
gravity runs inbox-digest
gravity logs
Show the logs of one hosted run. The run id comes from gravity runs.
gravity logs <run_id>
gravity tools
Browse the tool catalog: the names you can put under permissions.tools, with what each one does. It is the gateway's own list, so it shows exactly what a run can call, and it needs no sign-in.
31 tools are curated, such as gmail.read and web.search. The other ~1,370 are imported from the same apps and unreviewed: any imported tool that changes something must also be under approvals.required_for, and gravity validate checks that.
gravity tools [search]
gravity tools # everything
gravity tools gmail # only tools whose name contains "gmail"
gravity login
Sign in to Gravity. It opens your browser, shows a short code to confirm, and stores the session in your operating system's keychain.
gravity login
gravity logout
Remove the stored session.
gravity logout
gravity whoami
Show the account you are signed in as, and whether you hold a gateway token and until when (an expired one is a warning).
gravity whoami
gravity whoami --token # print the gateway token itself, for CI or a teammate's .env
gravity connect
Connect an account your agents' tools act on, through the app's own sign-in page in your browser. With no app, list what is connected.
gravity connect [app]
gravity connect # what is connected, and whose accounts runs act on
gravity connect gmail # opens the sign-in page, then waits until it is done
gravity connect sheets # use the app name from tool names: sheets, calendar, drive, linear, slack, github...
- Uses the gateway token from
gravity login. webtools need no account.
gravity guide
Read the builder guide, which ships with the CLI: how an agent is put together, the rules your code must follow, formatting results, approvals, adapters, scheduling, memory, and a checklist.
gravity guide [--save]
| Option | Description |
|---|---|
--save |
Write BUILDER_GUIDE.md to the current folder instead of showing it |
On a terminal it asks which section you want. Piped, it prints the whole guide.
gravity guide
gravity guide --save
gravity guide | less
gravity docs
Write or refresh the two files a coding agent needs to build for Gravity in this folder: Gravity.agent.md (how Gravity runs agents, the manifest, every tool this release knows, the output formats, the rules that fail a load) and problem-to-agent.md (the method for turning a problem statement into an agent that holds up).
gravity docs [--force]
| Option | Description |
|---|---|
--force |
Replace the files even where you edited them, or where a file Gravity did not write has the same name |
You rarely need this. Every command writes the two files into the folder it runs in if they are missing (never into your home folder or a drive root; gravity init writes them into the new agent), and rewrites them when a newer release changes them. The first line of a file Gravity wrote names the release and a hash of the text after it: if you edit the file the hash no longer matches and Gravity leaves it alone. Your own agent.md, AGENTS.md and CLAUDE.md are never touched. Point your coding agent at Gravity.agent.md from them if it does not look there by itself. Set GRAVITY_AGENT_DOCS=0 to stop the automatic writes.
gravity update
Check PyPI for a newer release, install it, and show what is new.
gravity update [--check]
| Option | Description |
|---|---|
--check |
Only say whether a newer release exists |
You do not have to run it: once every six hours a command looks for a newer release, and when there is one it says Update available 0.6.0 → 0.6.1, updates, and runs your command again as the new version, which lists what changed. It upgrades the way you installed it (uv tool or pipx); a copy installed any other way, or from a source checkout, is told the command to run and left alone. Nothing here can stop a command: a failed update is one warning and the command runs on the version you have. Set GRAVITY_AUTO_UPDATE=0 to turn the check and the install off; CI never updates.
gravity changelog
What changed in each release, written for builders.
gravity changelog [--all]
| Option | Description |
|---|---|
--all |
Every release, not only the one you are on |
gravity help
Show the list of commands. The same as gravity -h.
gravity help
Configuration
Where settings live
Put them in ~/.gravity/.env (on Windows, %USERPROFILE%\.gravity\.env). Every gravity command loads this file, from any folder, so you set a value once. With Gravity's hosted services, gravity login is all you need, and the file can stay empty.
# ~/.gravity/.env
GRAVITY_DEV_TOKEN=<a gateway token, only if gravity login did not issue you one>
That is the only setting most builders ever need. The CLI knows where Gravity's control plane is, and asks it where the gateway is.
When a setting is given in more than one place, the first match wins:
- a variable exported in your shell
~/.gravity/.env~/.gravity/config.json(only the API address, written bygravity --api-url URL <command>)- the built-in default
Never commit a .env file. The .gitignore in every template keeps .env and .gravity/ out of git.
What each task needs
| You want to | Commands | Required | Usually also set |
|---|---|---|---|
| Create, check and package an agent | init, validate, build |
nothing | nothing |
| Run an agent locally | run, schedule, adapt |
gravity login (or GRAVITY_DEV_TOKEN) |
nothing |
| Publish and inspect agents | push, agents, runs, logs, whoami |
gravity login |
nothing |
| Browse tools, connect accounts | tools, connect |
nothing for tools; gravity login for connect |
nothing |
Set GRAVITY_API_URL and GRAVITY_GATEWAY_URL only to work against services you run yourself; see Running a local gateway.
Environment variables
| Variable | What it is | Default |
|---|---|---|
GRAVITY_DEV_TOKEN |
A gateway token. gravity run trades it for a run token scoped to the manifest's tools. Overrides the token gravity login stored; needed only when login did not issue one, such as against a local gateway. |
the token from gravity login |
GRAVITY_GATEWAY_URL |
The gateway's MCP endpoint, ending in /mcp |
the gateway the control plane names in GET /api/cli/config |
GRAVITY_LLM_URL |
The OpenAI-compatible model endpoint | the gateway's address + /v1 |
GRAVITY_API_URL |
The control plane's address | https://agents.dev.gravity.fast |
GRAVITY_API_TOKEN |
A control-plane dev token, used instead of gravity login. For development only. |
none |
GRAVITY_CONFIG_DIR |
Moves ~/.gravity elsewhere. Useful for tests and CI. |
~/.gravity |
GRAVITY_AUTO_UPDATE |
When 0, commands neither check for a newer release nor install one. Always off when CI is set. |
1 |
GRAVITY_AGENT_DOCS |
When 0, commands do not write Gravity.agent.md and problem-to-agent.md into the folder they run in. gravity docs still does. |
1 |
GRAVITY_UPDATE_URL |
Where the latest version is read from, a JSON document with info.version. For tests and mirrors. |
PyPI |
GRAVITY_UNATTENDED |
When 1, calls that need approval are rejected without asking. gravity schedule sets it for you. |
unset |
What your agent receives
gravity run starts your agent with exactly these three, and the hosted runner does the same. You never set them yourself:
| Variable | Value |
|---|---|
GRAVITY_GATEWAY_URL |
the gateway's /mcp endpoint, for tools |
GRAVITY_LLM_URL |
the gateway's /v1 endpoint, for models |
GRAVITY_RUN_TOKEN |
a token for this run only. Use it as the bearer token for tools and as the API key for models. |
Authentication
Two separate credentials, for two separate services:
| Credential | Used by | How you get it |
|---|---|---|
| Control-plane session | push, tools, agents, runs, logs, whoami |
gravity login |
| Gateway token | run, schedule, adapt |
gravity login asks Gravity for one and stores it with the session. gravity whoami shows its expiry; gravity whoami --token prints it. GRAVITY_DEV_TOKEN, if set, is used instead. |
gravity login stores the session and gateway token in the operating system's keychain. On machines without one, it falls back to ~/.gravity/session.json, readable only by you. gravity logout removes it.
For development, setting GRAVITY_API_TOKEN to the control plane's dev token skips login. gravity whoami then prints the dev user's id followed by (dev token).
The manifest
manifest.yaml declares everything about the agent that Gravity enforces. Only name and runtime.entrypoint are required; everything else has a default.
The smallest useful manifest:
contract: v1
name: my-agent
version: 1.0.0
runtime:
entrypoint: src/agent.py:graph
inputs:
- name: query
type: text
label: "What should the agent do?"
required: true
permissions:
tools: [example.echo]
A complete manifest, with every section annotated
This one is valid as written.
contract: v1 # manifest contract version; v1 is the only one
name: inbox-digest # lowercase letters, digits, single hyphens; appears in URLs
version: 1.0.0 # semver; a published version is frozen, so bump it to publish again
description: "Summarises unread email and sends a digest"
kind: interactive # transform | interactive | monitor
runtime:
framework: langgraph # the only framework in v1
python: "3.12" # the platform image runs Python 3.12
entrypoint: src/agent.py:graph # file:object, relative to the project folder
timeout_seconds: 300 # 10 to 28800
dependencies:
lockfile: requirements.lock # written by `gravity validate`
inputs: # each becomes a field in the consumer's form
- name: lookback
type: static-dropdown
label: "How far back?"
options: ["1 day", "7 days"]
default: "1 day"
- name: send_to
type: email
label: "Send the digest to"
required: true
permissions:
tools: # the only tools this agent's runs may call
- gmail.read
- gmail.send
models:
tier: standard # standard | premium
approvals:
required_for: # these calls pause until a person approves them
- gmail.send
resources:
memory_mb: 512 # 128 to 4096; a request the platform may lower
triggers:
manual: true # the consumer can start a run
schedule:
cron: "0 8 * * 1-5" # weekdays at 08:00, Asia/Kolkata
consumer_can_change: true
state:
max_kb: 64 # memory kept between runs (1 to 256 KB); omit for none
slots: # a part the consumer chooses; each adapter gets only its own tools
deliver:
label: "Where should the digest go?"
default: email
adapters:
email: [gmail.draft] # code in src/adapters/deliver/email.py
slack: [slack.send] # code in src/adapters/deliver/slack.py
outputs: # extra formats the platform renders and delivers itself
report:
renderer: markdown # markdown | docx | pptx | rows
label: "Digest as a document"
destinations: [download]
Field reference
| Field | Required | Default | Rules |
|---|---|---|---|
contract |
no | v1 |
Only v1 exists. |
name |
yes | Lowercase letters, digits and single hyphens, up to 64 characters. Appears in URLs. | |
version |
no | 1.0.0 |
Strict semver, such as 1.2.0. A published version can never change. |
description |
no | empty | Up to 500 characters. |
kind |
no | interactive |
transform (input in, result out), interactive (calls tools mid-run, may converse) or monitor (runs on a schedule or webhook and remembers the last run; needs a trigger). |
runtime.entrypoint |
yes | path/to/file.py:object, inside the project. The object is your graph factory. |
|
runtime.framework |
no | langgraph |
Only langgraph. |
runtime.python |
no | 3.12 |
What the platform image runs. gravity validate fails on any other value, because the hosted build would. |
runtime.timeout_seconds |
no | 300 |
10 to 28800. |
dependencies.lockfile |
no | requirements.lock |
Compiled by gravity validate; ships with the agent. |
inputs[] |
no | none | Up to 50. Each has name (a lowercase identifier, the key in state["inputs"]), type, label, and optionally required, default, options, accept. |
inputs[].type |
yes | text, textarea, number, boolean, static-dropdown (needs options), email, url, date, secret (an API key, password or other confidential value) or file (no default; accept lists extensions such as [pdf, csv]). |
|
permissions.tools |
no | none | Up to 50 catalog names, such as gmail.read. Run gravity tools for the list. |
permissions.models.tier |
no | standard |
standard or premium. |
approvals.required_for |
no | none | Tools that pause until a person approves. Each must also be declared under permissions.tools or a slot adapter. |
resources.memory_mb |
no | 512 |
128 to 4096. A request; the platform may lower it. |
triggers.manual |
no | true |
Whether the consumer can start a run. Something must start the agent: manual, a schedule or a webhook. |
triggers.schedule |
no | none | cron (5 fields, Asia/Kolkata, such as "0 9 * * 1"; every 5 minutes at the fastest; day of month or weekday, not both) and consumer_can_change (default true). |
triggers.webhook |
no | none | A list of events; github.push is the only one today. |
state.max_kb |
no | no memory | Include state: to keep memory between runs, in state["memory"]. 1 to 256 KB. |
slots.<name> |
no | none | A swappable part: label, default adapter, and adapters mapping each adapter name to the tools it may call. Code lives in src/adapters/<slot>/<adapter>.py. Slot names cannot repeat an input's name. |
outputs.<name> |
no | none | Up to 8 extra formats rendered from the result: renderer (markdown, docx, pptx or rows), label and destinations (download, or a catalog tool such as gmail.send). |
Unknown fields are rejected rather than ignored, so a typo is reported instead of silently dropped.
An agent project
my-agent/
├── manifest.yaml what the agent is and may do
├── requirements.txt your dependencies
├── requirements.lock compiled by `gravity validate`; commit it
└── src/
└── agent.py exports `graph`, an async factory returning a compiled LangGraph graph
Files the CLI writes:
| Path | Written by | Contents |
|---|---|---|
requirements.lock |
validate |
Exact dependency versions for the platform image |
build/agent.zip, build/build.json |
build |
The bundle push uploads, and its hashes |
.gravity/payload.json |
run |
The inputs passed to the agent |
.gravity/thread.json |
run |
The last conversation of a chat agent, for --reply |
.gravity/memory.json |
run |
Memory kept between runs, when the manifest declares state |
.gravity/payload.json |
run, dev |
The run's inputs for the runner; deleted when the run ends (run.json keeps them with secret inputs redacted) |
.gravity/tool_schemas.json |
adapt |
Argument schemas of every tool the slot uses, read from the gateway |
The template .gitignore excludes build/, .gravity/ and .env.
Running a local gateway
The gateway belongs to the Gravity platform repository and is run by the Gravity team. Builders outside the team use the hosted gateway instead.
Gateway settings, for the Gravity team
Start it with gravity-gateway serve. It listens on port 8000, prints a dev token, and reads its own .env in the gateway's folder. With nothing set, it serves only the built-in example.echo tool and keeps tokens in memory.
| Variable | What it enables | When you need it |
|---|---|---|
COMPOSIO_API_KEY |
Real tools (Gmail, Slack, Linear and others). Unset: only example.echo. |
To call any real tool |
OPENROUTER_API_KEY |
Model calls on /v1. Unset: /v1 returns 503. |
For any agent that calls a model |
OPENROUTER_BASE_URL |
Where model calls go. Default https://openrouter.ai/api/v1. |
Rarely |
LOCAL_DEV_TOKEN |
A fixed dev token (24+ random characters), so restarts don't change it. Use the same value as GRAVITY_DEV_TOKEN. |
Recommended |
GATEWAY_PORT |
The listening port. Default 8000. |
Only if 8000 is taken |
DATABASE_URL, PLATFORM_DB_SCHEMA |
Keeps run tokens in the platform database instead of memory | Platform development |
PLATFORM_SERVICE_KEY, AWS_REGION |
Lets hosted runs mint tokens and report back | Platform development |
A fully local setup, in ~/.gravity/.env:
GRAVITY_DEV_TOKEN=<the gateway's LOCAL_DEV_TOKEN>
GRAVITY_GATEWAY_URL=http://127.0.0.1:8000/mcp
GRAVITY_API_URL=http://localhost:3010
GRAVITY_API_TOKEN=<the control plane's PLATFORM_DEV_TOKEN>
Real tools act on a connected account. Connect one with gravity-gateway connect --user-id builder:local --toolkit <app>.
Troubleshooting
| Message | Cause and fix |
|---|---|
No matching distribution found for gravity-cli |
pip is running on a Python older than 3.12. Use uv tool install gravity-cli, or see Installation. |
No gateway token |
run, schedule and adapt need a gateway token. Run gravity login, or add GRAVITY_DEV_TOKEN to ~/.gravity/.env. |
Your gateway token has expired |
The token gravity login stored has lapsed. Run gravity login again. |
Can't reach the gateway at … |
No gateway at that address. Set GRAVITY_GATEWAY_URL to your gateway's /mcp address. |
The gateway refused this run |
The gateway rejected the token or a declared tool. Run gravity whoami to check the token, or gravity login for a fresh one. |
You are not signed in |
A control-plane command without credentials. Run gravity login. |
Can't reach Gravity at … |
The control plane did not answer. Check GRAVITY_API_URL in ~/.gravity/.env, or api_url in ~/.gravity/config.json (set by gravity --api-url). |
Gravity refused GRAVITY_API_TOKEN |
The dev token in ~/.gravity/.env is wrong or revoked. Fix it there, or remove it and run gravity login. |
… did not answer with JSON or Gravity answered 5xx at … |
A proxy, VPN sign-in page or outage is answering instead of Gravity. Check GRAVITY_API_URL; for a 5xx wait a minute and retry, then tell Gravity if it persists. |
Another run is live |
Runs in one folder take turns. Wait for it, or stop it; if none is live, delete .gravity/run.lock. |
Manifest is not UTF-8 text |
Notepad saved it as "Unicode" (UTF-16). Re-save it with Encoding: UTF-8. |
could not load the agent: No module named … only on Windows |
Windows limits paths to 260 characters and some packages in .venv exceed it from a deep folder. Move the agent to a shorter path or enable long paths. |
Missing required input: … |
Pass each required input with --input name=value, or give it a default in the manifest. |
No build to push or The build is stale |
Run gravity build again after any change to src/, the manifest or the requirements. |
✗ requirements.lock in gravity validate |
A dependency has no prebuilt package for the platform image (ARM64 Linux). Pick a version that publishes one, or a different package. |
A tool row shows refused — not in permissions.tools |
The agent called a tool its manifest does not declare. Add it under permissions.tools, then run again. |
What's new
- 0.6.0 Gravity CLI keeps itself up to date and says what changed (
gravity update,gravity changelog).Gravity.agent.mdandproblem-to-agent.mdappear in the folder you work in, for your coding agent (gravity docs).gravityon its own shows your version, the agent in the folder, whether you are signed in, and any update. The notes shown after an update aresrc/gravity_cli/CHANGELOG.md. - 0.5.5
manifest.yamltakes an optionaldisplay:hint for how a run's result shows:view(document, report, table, chart, code or files),field(the result key to show) andchart(type: lineorbar,x, and up to threeyfields). It is a hint, never a rule: an unknown view or key is ignored, so it never fails a push. 0.5.4 and earlier refuse a manifest that has it. - 0.5.4
gravity agentsshows each agent's review: a REVIEW column (not submitted, submitted, approved, rejected), and under "From the Gravity team" the reviewer's notes and what to do next. An agent the Gravity team turned off shows asoffwith the reason. UPDATED reads "3 min ago" instead of a timestamp, and status and review are coloured. Also, from a full audit of every command:gravity --version. Clear messages instead of tracebacks for: a proxy or sign-in page answering instead of Gravity, a refusedGRAVITY_API_TOKENorGRAVITY_DEV_TOKEN, an expired sign-in code, a non-UTF-8 manifest, an out-of-range--port, a missing agent folder, a malformed session file.gravity validatenow refuses what the build would refuse: agraphthat is not an async factory,os.environorasyncio.runat import, relative imports, unquotedpython: 3.12, schedules faster than every 5 minutes or with both day fields, out-of-range cron fields; it accepts a re-exportedgraph.gravity agentsshows the review status and failure reasons;gravity runsnever shortens an id;gravity logsnever wraps.gravity pushrefuses a bundle over 30 MB or a manifest over 12 KB before uploading, and says why when a failed version is re-pushed.gravity buildshipstools_needed.yaml, leaves dotfiles out, and refuses to build withoutrequirements.lock.gravity adaptleaves the folder untouched when a model call fails.gravity schedulechecks inputs before the first tick and runs on Asia/Kolkata like the hosted schedules.gravity rundeletes.gravity/payload.jsonafter the run, warns when no.venvis present, and keeps a plain agent'sresult.gravity connect <app>refuses on the shared test account instead of attaching your account to it. The builder guide and agent standard describe the real flow (init, validate, run or dev, build, push, review on /app). - 0.5.3
gravity devopens a local dashboard for your agent: a form from your manifest, a live timeline, approvals, the result and run history; runs fromgravity runin the same folder show up in it live, and the other way round. WhatsApp joins the catalog (51 tools; 1,402 across 15 apps including the curated and built-in ones). Agents can return generated images: put them in a corpus section'simagesand they land in the Word, PowerPoint and Markdown outputs. The agent standard now shows how to generate images and embeddings through the gateway.NEEDS_RECONNECTon the shared test account tells you to ask Gravity, not to connect your own account. - 0.5.2 Jira, Confluence and Notion join the catalog: 1,351 tools across 11 apps.
get_store()works in local runs, kept in.gravity/store.json. Renewing an expired sign-in keeps your gateway token, sogravity runkeeps working after an hour. Commands start faster: the catalog loads in a quarter of the time. - 0.5.0
gravity connectlinks your own accounts.web.searchandweb.fetchjoin the catalog.gravity validatenow refuses a tool that isn't in the catalog, an imported write tool without an approval, and code that names a write tool the manifest doesn't grant.gravity toolsreads the gateway's list and needs no sign-in. On Windows the session stays in the keychain instead of a file. - 0.4.4 Same code as 0.4.3, released from this repository so the GitHub release, the tag and PyPI line up again. Adds
RELEASING.md. - 0.4.3 Documents the
secretinput type and thesupadata.get_transcripttool. - 0.4.1 New input type
secretfor an API key, password or other confidential value. New built-in toolsupadata.get_transcript(gravity tools supadata).
Limitations
- No offline mode.
gravity runalways talks to a real gateway. There is no mock-tool mode. - Heuristic drift check.
gravity validatefinds undeclared tools by scanning source for imports and tool-shaped strings. Expect some false positives; they are warnings, never failures. - Models are not restricted yet. The gateway forwards whatever model name the agent asks for, and
permissions.models.tieris not enforced. - Hosted history.
gravity runsandgravity logsshow what the control plane returns, which is empty until an agent has hosted runs.
Development
git clone https://github.com/AIGravity/gravity-cli.git
cd gravity-cli
uv sync # both packages and the dev tools
uv run gravity --help
uv run pytest # CLI tests
cd packages/gravity-schema && uv run pytest && cd ../.. # manifest contract tests
uv run ruff check .
The repository holds two packages that are always released together at the same version:
| Package | What it is |
|---|---|
gravity-cli |
The gravity command (src/gravity_cli/) |
gravity-schema |
The manifest contract: models, validation, the tool catalog and its JSON Schema (packages/gravity-schema/). The platform's servers use it too, so a manifest is judged the same way everywhere. |
After changing the manifest model, regenerate the committed JSON Schema with uv run python -m gravity_schema. A test fails until you do.
Continuous integration and releasing
Every pull request and every push to main runs ci.yml:
ruff check, and a check thatuv.lockmatchespyproject.toml- both test suites on Ubuntu and Windows, with Python 3.12 and 3.13
- a build of both packages, installed into a clean environment and used for real
To release, follow RELEASING.md, which has every command and what to do when a step fails. In short:
- Set the new version in three places:
versioninpyproject.toml,versioninpackages/gravity-schema/pyproject.toml, and thegravity-schema==pin inpyproject.toml. Runuv lock, then merge tomain. - Tag the merge commit and push the tag. A tag containing
rc, such asv0.2.0rc1, publishes to TestPyPI. Any otherv*tag, such asv0.2.0, publishes to PyPI and creates a GitHub Release.
release.yml runs the full CI first, refuses a tag that does not match all three versions, and publishes with PyPI trusted publishing, so no PyPI token is stored anywhere.
Each package is published from its own GitHub environment:
| Index | Project | Environment |
|---|---|---|
| pypi.org | gravity-cli |
pypi |
| pypi.org | gravity-schema |
pypi-schema |
| test.pypi.org | gravity-cli |
testpypi |
| test.pypi.org | gravity-schema |
testpypi-schema |
To install a release candidate, whose dependencies live on the real PyPI:
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ gravity-cli==0.2.0rc1
After changing catalog.yaml, regenerate the tool lists in the platform repository:
uv run python -m gravity_schema.gen_catalog --root <platform repo>/code-first
Documentation
| Document | What it covers |
|---|---|
| Builder guide | How to build a good agent, start to finish, with a complete example. Also gravity guide |
Gravity.agent.md |
The guide gravity docs writes for coding agents; its tool list is filled in from the catalog of the release that writes it |
problem-to-agent.md |
The method for turning a problem statement into an agent that holds up |
| Agent standard | The exact rules an agent must follow |
| Templates | The four templates and when to pick each |
manifest.schema.json |
The manifest as a machine-readable JSON Schema |
PyPI · Apache License 2.0 · Copyright 2026 AIGravity · See LICENSE and NOTICE
Metadata
Release files for gravity-cli 0.7.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| gravity_cli-0.7.3.tar.gz | 415.1 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| gravity_cli-0.7.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 614.2 kB
Release files / gravity_cli-0.7.3.tar.gz
| Download URL | gravity_cli-0.7.3.tar.gz |
|---|---|
| Size | 415.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d86855d6186cf194f05e215ef9eef197a55232a7d6ad0ca9be7fa285fb997fc4
|
|
BLAKE2b-256 checksum How to use checksums |
a07fb8d36c98dd6e6b5beb3610f972041d2fdfeb4dd464caa5de332d9e532565
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logRelease files / gravity_cli-0.7.3-py3-none-any.whl
| Download URL | gravity_cli-0.7.3-py3-none-any.whl |
|---|---|
| Size | 199.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7f2bee8f0d21bcff5522a93955d89ae2db65b12f7f80f6f2876ba8bd0265a613
|
|
BLAKE2b-256 checksum How to use checksums |
5bc15d5b2f81917758f1e2adf5e6899fb1f72b5170c16ba5f44d2451f769a2d8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logSecurity for gravity-cli 0.7.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for gravity_cli-0.7.3.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.