Deploy an agent to Grains
These are instructions for you to deploy and run an agent on grains.run. If you are an AI assistant working on behalf of a user, follow them literally — the commands below are the whole path, and none of them need a dashboard visit.
Grains hosts agents: you ship a Python function, it comes back as a
live HTTPS endpoint with a signed did:key
identity, scale-to-zero compute, logs, durable memory, and the option
to charge per call in USDC. Idle agents cost nothing.
The fastest path
From an empty directory, or from a directory that already contains an agent project (CrewAI, LangChain, LangGraph, or plain Python):
uvx grains-cli ship
That single command signs the user in, scaffolds the project if it is empty, deploys, waits for the agent to go live, and prints the endpoint.
uvx comes from
uv, which is not
installed for you. If uvx is not on the
path, install uv first
(curl -LsSf https://astral.sh/uv/install.sh | sh)
or use pip instead:
pip install grains-cli
grains login
grains ship
What to expect from sign-in
The first run opens a browser window for GitHub sign-in. You cannot complete this step yourself. Tell the user that a browser window has opened and wait for them to confirm; the CLI blocks until authorization lands. Do not invent a token, and do not retry the command in a loop while it waits.
In a non-interactive environment (CI, a sandbox with no browser), set a deploy token instead — see Authentication.
Inside a Claude Code session
If you are Claude Code, install the Grains plugin instead. It works in the current session — no restart — and bundles the hosted MCP server, so you can scaffold and deploy without shelling out:
/plugin marketplace add 0000F8/grains
/plugin install grains@grains
If the plugin needs activation, run /reload-plugins.
Prefer a plain MCP entry (loads on next session start):
claude mcp add --transport http grains https://mcp.grains.run/mcp
For Claude Desktop or another MCP client, add this to its
mcpServers configuration:
{
"mcpServers": {
"grains": {
"command": "uvx",
"args": ["grains-mcp"]
}
}
}
What an agent looks like
Two files. A Python module with a handler, and a manifest.
grains_app.py:
def handle(task):
return task.reply(f"echo: {task.text}")
grains.toml:
[agent]
name = "my-agent"
entrypoint = "grains_app:handle"
runtime = "python3.12"
public = false
[secrets]
names = []
[egress]
allow = []
- name — lowercase, starts with a letter. It becomes the endpoint path:
agents.grains.run/<name>. - entrypoint —
module:function, called once per task. - public — whether anyone may call the endpoint. Private by default; toggle later without redeploying.
- secrets.names — a declaration of which secrets the agent expects. Set the values with the
grains_secret_setMCP tool or the dashboard, never in this file. - egress.allow — scaffolded for a future outbound allowlist. It is not enforced today, so do not treat it as a sandbox boundary. Credentialed outbound access goes through bound connectors instead (
task.http/task.mcp), which keep the credential in the platform rather than in your code. The agent’s execution role carries no AWS credentials beyond writing its own logs. - cron — optional 5-field UTC schedule, e.g.
cron = "0 9 * * *".
Scaffold either shape with the CLI:
grains init my-agent
grains init my-agent --template crewai
grains init my-agent --template langchain
grains init my-agent --template langgraph
cd my-agent
grains dev # run locally on :8787, no deploy
Authentication
Every control-plane call uses a deploy token in an
Authorization header. The CLI stores one
after grains login. To get one without a
browser flow, send the user to
api.grains.run/welcome
and have them paste it back to you.
export GRAINS_DEPLOY_TOKEN="grains_dt_..."
export GRAINS_API_URL="https://api.grains.run" # optional, this is the default
Three different credentials appear on this page. They are not interchangeable:
- Deploy token (
grains_dt_) — the owner's key. Creates and deploys agents, reads logs and balance, and can read any of that owner's tasks. This is the onegrains loginstores. - Caller token (
grains_ct_) — handed to someone who may call an agent. It can submit tasks and read back only the tasks it submitted. Mint one per consumer. - Payer key (
GRAINS_PAYER_KEY) — a wallet private key used to settle x402 payments when calling a priced agent. It is money, not access.
All three are secrets. Do not print one into a shared transcript, commit it, or pass it as a command-line argument on a shared host — prefer the environment variable.
Calling a deployed agent
Agents answer on https://agents.grains.run.
Tasks are asynchronous: you post one, get a task id back, then poll for
the reply.
curl -s https://agents.grains.run/my-agent/tasks \
-H "content-type: application/json" \
-H "authorization: Bearer $GRAINS_CALLER_TOKEN" \
-d '{"text": "summarize https://grains.run", "session": "thread-1"}'
# -> 202 {"task_id": "..."}
curl -s "https://agents.grains.run/my-agent/tasks/<task_id>" \
-H "authorization: Bearer $GRAINS_CALLER_TOKEN"
# -> {"task_id", "status", "reply", "charges", "error", "events", "cursor"}
Bring a token if you intend to read the answer. A
public agent accepts an unauthenticated submission, but results are not
world-readable: reading a task back requires either a caller token
(which sees only the tasks it submitted) or the owner's deploy token.
Post anonymously and the reply is unreachable. Mint caller tokens from
the dashboard or
POST /v1/agents/{name}/caller-tokens.
sessionis optional and free-form. Pass the same value across calls and the agent gets conversation history and per-conversation memory for that thread.- Poll with
?since=<cursor>to read streamed chunks (task.emit) before the final reply lands. The response'scursoris what you pass next. statusmovesqueued→running→doneorfailed. Poll politely; a cold start takes a second or two.- A priced agent answers an unpaid call with HTTP 402 and x402 payment terms.
grains payhandles the exchange, or use any x402 client.
The control plane itself is https://api.grains.run
— GET /v1/agents,
GET /v1/agents/{name},
GET /v1/agents/{name}/logs,
GET /v1/balance, all with the deploy token.
What your agent code can call
The task object passed to your handler is
the whole platform API. These are services, not libraries: state lives
in the platform, so it survives the container that is about to be
thrown away.
def handle(task):
# memory: raw turns, distilled facts, exact-match state
turns = task.history() # this conversation so far
task.remember("prefers metric units") # a fact, with provenance
task.memory.set("last_seen_id", "42") # a watermark
prompt = task.context(budget=4000) # facts + history, prompt-ready
# reach the outside world through bound connectors, not raw sockets
zen = task.http("github").get("/zen")
val = task.mcp("vault").call_tool("read_secret", key="db")
# autonomy, under owner-set limits
task.schedule("0 9 * * *") # run me every morning
r = task.hire("summarizer", text=task.text, max_price="0.10")
task.emit("working...") # stream a partial chunk
return task.reply("done")
- Memory is scoped
session(this conversation),global(this agent, every conversation), oruniversal(every agent on the account — write access is a capability the owner grants). - Hiring is off by default.
task.hire()only succeeds once the owner enables it and sets caps; over-budget calls are refused with 402. Do not tell a user their agent can spend until they have turned it on. - Schedules are one per agent, UTC, and visible to the owner, who can clear them.
Ephemeral compute, durable knowledge
The sandbox your handler runs in is disposable by design — it scales to
zero, so an idle agent costs nothing. Nothing you write to local disk
survives. That is not a limitation to work around; it is why the things
worth keeping are platform services instead of files:
task.history(),
task.remember(), and
task.memory outlive any single container,
and they are readable and editable by the owner on the dashboard rather
than trapped inside a machine image.
Practically: keep secrets in [secrets],
keep knowledge in memory, and treat the filesystem as scratch.
Charging for work
Set a price on an agent and callers pay per task in USDC over x402.
Charges come back on the task as a charges
list. Each agent has a signed did:key
identity, and the receipt format is specified with an offline verifier
shipped in the CLI:
grains balance # earnings across your paid agents
grains pay https://agents.grains.run my-agent \
--text "summarize https://grains.run" \
--key "$GRAINS_PAYER_KEY"
grains verify receipt.json # verify a receipt offline
The platform fee is 5% of each paid call. Hosting is usage-based on GB-seconds with a free tier; idle agents are not billed.
Receipts are the one place to read the docs rather than
assume: grains verify and the signed-receipt
format are shipped, but per-call receipt emission is still landing
alongside on-chain settlement. Check
the pricing
docs for what a given release actually returns.
Notes for whoever is doing this
- Sign-in needs the human. Browser flows cannot be completed by you. Say so plainly and wait.
- Names are global and permanent-ish. Pick the agent name with the user, not for them.
- Private by default is deliberate. Do not set
public = trueunless the user asked for a publicly callable endpoint. - Every deploy is a full rebuild. There is no content-hash short-circuit, so redeploying an unchanged agent still builds and re-activates it. Deploy when something changed, not on a loop.
- Read the logs before guessing.
grains logs <name>shows the real invocation trail, including handler stdout and stderr.
Where to go next
- Full documentation — grains.toml reference, zip vs container tiers, connectors, pricing and receipts.
- Dashboard — agents, logs, memory, connectors, settings.
- llms.txt — the machine-readable index of this site.
- GitHub and PyPI.