For agents
How an agent discovers, previews, and safely executes any Bittensor operation.
The SDK and CLI are built to be driven by agents. Every operation is
discoverable at runtime with a JSON schema, every mutation can be previewed
before it spends anything, every failure returns a machine-readable code with a
remediation hint, and a Policy can hard-bound what a session is allowed to
do. Nothing here requires parsing human prose.
Searching these docs
Start with one fetch: /llms.txt. Every docs link in it is
already raw markdown (content.md), so you can open a page without rewriting
URLs or scraping HTML.
# Curated index (start here)
curl -s https://www.bittensor.com/llms.txt
# Full docs corpus (for rg, not for stuffing into context)
curl -s https://www.bittensor.com/llms-full.txt | rg -n '<pattern>'
# One page (same URLs as the links inside llms.txt)
curl -s https://www.bittensor.com/llms.mdx/docs/quickstart/content.md
# Chain source: search by symbol, or fetch a known path
curl -s 'https://www.bittensor.com/code/search.json?q=do_move_stake'
curl -s https://www.bittensor.com/code/raw/pallets/subtensor/src/staking/move_stake.rs
# Heavy exploration: clone the repo (docs live in docs/)
git clone --depth 1 https://github.com/RaoFoundation/subtensorPrefer that index plus the JSON catalogs below over dumping
/llms-full.txt into context.
Discover the operations
btcli tools # JSON: every transaction op + summary, description, signer, input schemaimport bittensor as bt
bt.intents.list_tools() # same catalog, as Python dicts
bt.reads.list_reads() # every read: name, params, docs, categoryThe same catalogs are published statically by these docs:
/catalog/intents.json,
/catalog/reads.json,
/catalog/errors.json. Each entry includes
docs_url (HTML), markdown_url (raw markdown to fetch), and often
sources / hits with a raw_url into /code. Errors live under
.codes (semantic) and .chain_errors (exact pallet names); see .note.
Execute by name
An agent never needs to import intent classes — build and execute by op name with a plain dict, validated against the schema:
async with bt.Subtensor() as client:
result = await client.execute_tool(
"transfer", {"dest_ss58": "5F...", "amount_tao": 1.0}, wallet
)On the CLI, every op in the catalog is btcli tx <op-name> (underscores
become dashes) and every read is btcli query <name>.
Preview before you spend
plan (SDK) and --dry-run (CLI) run the full pipeline — fee estimation,
predicted effects, warnings, policy check — without submitting:
plan = await client.plan(intent, wallet)
plan.fee # estimated fee
plan.effects # list[str]: what this will do
plan.warnings # non-fatal cautions (e.g. dust amounts)
plan.ok # would policy allow it?Bound the blast radius with Policy
Attach a Policy to the client and every execution — by class, by name, or
raw — passes through one choke point:
policy = bt.Policy(max_spend_tao=5.0, allowed_netuids=[1, 2])
async with bt.Subtensor("finney", policy=policy) as client:
... # anything exceeding the caps raises PolicyError at execute timeOperations whose cost cannot be bounded ahead of time (e.g. subnet
registration, whose price floats) are blocked by a spend cap until it is
raised — the safe default. Raw calls are refused unless the policy sets
allow_raw_calls=True.
Branch on failures, don't parse them
Every write returns an ExtrinsicResult; failures carry a semantic
ErrorCode and a remediation hint:
result = await client.execute(intent, wallet)
if not result.success:
match result.error.code:
case bt.ErrorCode.RATE_LIMITED: ... # wait and retry
case bt.ErrorCode.INSUFFICIENT_BALANCE: ... # reduce amount
case _:
log(result.error.remediation) # actionable next step, always presentCLI rules for non-interactive use
--jsonon any command produces machine-readable output.--yesskips confirmation prompts. Without it, a non-interactive session is declined, not blocked — the CLI never hangs waiting for input it can't get.--dry-runpreviews anytxcommand.- Configuration comes from flags,
BT_*environment variables, orbtcli config— highest to lowest precedence.
The escape hatch
Anything on chain that no intent wraps (deprecated, root/admin-only, or off-chain-signed calls) is still reachable:
call = bt.calls.Commitments.set_commitment(netuid=1, info={...})
await client.submit_call(call, wallet, signer="hotkey")And generic accessors cover any storage item, constant, or runtime API in the chain metadata:
tempo = await client.query(bt.storage.SubtensorModule.Tempo, [1])
ed = await client.constant(bt.constants.Balances.ExistentialDeposit)