AGENTS.md

September 16, 2026 · View on GitHub

Primary entry point for any AI agent working in this codebase. Also read CONTRIBUTING.md for general contribution guidelines.


Project Overview

Gno (Gnolang) is a deterministic, interpreted variant of Go for writing smart contracts. gno.land is the L1 blockchain that runs Gno code.

PillarDirectoryWhat it does
GnoVMgnovm/Interpreter/VM, parser, type-checker
gno.landgno.land/Blockchain node, ABCI app, SDK, CLI tools
Tendermint2tm2/Consensus, p2p, mempool, crypto, RPC

Other directories: examples/ (sample realms/packages), contribs/ (tools: gnodev, gnofaucet, etc.), misc/, docs/, .github/.


.gno Files Are NOT Go

  • .gno looks like Go but runs in a deterministic VM.
  • No goroutines, channels, unsafe, net, os.
  • Realms (r/) are stateful (on-chain state).
  • Packages (p/) are stateless libraries.
  • Import paths: gno.land/p/... or gno.land/r/...never github.com/....
  • Module config: gnomod.toml (not go.mod).
  • cross(rlm) form for cross-realm calls.
  • realm type for realm-aware functions.

Build & Test

make install                    # Install gnokey, gno, gnodev
make test                       # Run all tests (slow)
make lint                       # golangci-lint
make fmt                        # Format all code
Gno testsGo tests
gno test ./path/...go test ./...
Test files: _test.gnoTest files: _test.go
Filetests: _filetest.gnoIntegration: .txtar in gno.land/pkg/integration/testdata/
VM file tests: gnovm/tests/files/ with // Output:Standard Go test runner

Verification Rules

  • After changing anything under gnovm/stdlibs/, gnovm/tests/stdlibs/, or the VM's execution context, always run the Gno example suite before declaring done:
    • cd examples && go run ../gnovm/cmd/gno test ./... Go tests do not cover .gno filetests. A change can pass every go test target below and still leave examples/ red — stdlib behavior reaches filetests through a different path.
  • After changing gas constants or allocation/GC logic, always run these before declaring done:
    • go test ./gno.land/pkg/sdk/vm/ -run Gas
    • go test ./gno.land/pkg/integration/ -run TestTestdata
    • go test ./gnovm/pkg/gnolang/ -run Files -test.short
  • Always run /simplify before presenting completed work on non-trivial changes.
  • When comparing gas numbers or performance metrics before vs after, always verify the test logic has not changed, including loop counts and input sizes. Show reasoning, not just numbers.
  • Never claim a percentage improvement without confirming the test is doing the same work in both cases.

Conventions

Gno (.gno)

  • Format: gno fmt.
  • Import paths: gno.land/{p,r}/... only.
  • No OS/network access.
  • Before writing Gno code, read the relevant docs in docs/resources/:
    • gno-interrealm.md — cross-realm calls, cross(rlm)/realm semantics
    • effective-gno.md — idiomatic Gno patterns
    • go-gno-compatibility.md — what works and what doesn't vs Go
    • gno-testing.md — testing patterns for .gno files
    • gno-packages.md — package structure and conventions
    • gno-security-guide.md — security patterns and known vulnerability families (see Gno Security Semantics below)

Go (.go)

  • Format: gofmt/goimports.
  • Lint: golangci-lint (.github/golangci.yml).

Universal

  • Commits: conventional (feat:, fix:, docs:, chore:, test:, refactor:) with optional scope (feat(gnovm):).
  • Branches: kebab-case (fix-vm-params), always feature branches.
  • If posting comments or reviews under your owner's GitHub account fully autonomously (no human reviewing the output before it is posted), disclose that you are an AI agent (e.g., prefix with [bot] or add a "generated by" note) so humans can distinguish agent activity from the account owner. Human-assisted contributions that the account owner reviews and approves before posting do not need this disclosure.
  • Don't credit the agent as a commit author (Co-Authored-By, Assisted-By, "Generated by"); the human author owns the commit. Note AI usage in the PR description if relevant.
  • When writing PR descriptions, grep for all new/modified files in the diff (git diff --stat) and categorize them. Do not omit major new files like benchmarks, tooling, or calibration scripts.
  • List all categories of work in PR descriptions, including features, bug fixes, tooling, and tests, not just the headline feature.
  • Keep PRs up to date with master — merge regularly and resolve conflicts promptly.
  • When merging master into your branch, review incoming changes carefully. If CONTRIBUTING.md or AGENTS.md was updated, re-read and follow the latest rules.
  • Be respectful and helpful in PR comments and reviews — optimize for the reviewer's time, especially human maintainers.
  • See CONTRIBUTING.md.

Gno Security Semantics

  • Before writing or reviewing any caller-authentication, access-control, or cross-realm code in Gno (/r/, /p/, /e/ packages), read docs/resources/gno-interrealm.md. Do not pattern-match from Solidity msg.sender or other-language intuition.
  • For the complete AI-focused checklist (11 cases), read docs/resources/gno-ai-contract-review.md before reviewing any realm.
  • Reading one balance is banker.GetCoin(addr, denom), not GetCoins(addr).AmountOf(denom). GetCoins reads every denom the address holds, and since anyone can send any address a new denom without consent, its cost is set by a third party — on a caller-supplied address that is a permanent out-of-gas waiting to happen. Hoisting a repeated GetCoins out of a loop helps but does not fix it: the second call's per-key reads are cache hits and cost no gas, while its iterator walk is charged again, so what remains still scales with a denom count a third party controls. Two exceptions, both real: GetCoin panics on a malformed denom where AmountOf returns zero, so do not put an unvalidated Render path segment or query parameter through it; and if the surrounding function already called GetCoins, use its result rather than reading again. Both statements are about the realm-facing chain/banker API — the Go keeper's GetCoin and std.Coins.AmountOf behave the other way round, so do not carry a measurement from one side over to the other.
  • runtime.PreviousRealm() only shifts on explicit cross-calls (fn(cross, ...)) into crossing functions (func fn(cur realm, ...){...}). A PreviousRealm().PkgPath() == "..." check inside a non-crossing function does not identify the immediate caller and is a security bug.
  • In a crossing function, derive caller identity from cur.Previous() and write no cur.IsCurrent() guard: the runtime puts cur into the current realm-context on entry, so the check is always true. IsCurrent() belongs on a realm value a caller hands over as an ordinary argument, rlm by convention, which may be left over from an earlier call.
  • Never read chain/runtime/unsafe.PreviousRealm() in a crossing function: it walks the frame stack, so it answers from wherever it is called, while cur.Previous() names the realm that was current when cur was minted. Delete any chain/runtime/unsafe import sitting beside a cur realm parameter.
  • When editing a realm that accepts payment via banker.OriginSend(), the caller guard must be cur.Previous().IsUserCall(), not IsUser(). IsUser() accepts maketx run ephemeral realms, which can consume the origin-send envelope before calling your function and bypass the payment check. See docs/resources/effective-gno.md § Verifying inbound Coin payments.
  • When you see an existing realm using IsUser() plus banker.OriginSend(), flag it and cross-check nearby OriginSend usage.
  • Never return a pointer to a /p/-type instance stored in realm state if that type has any exported mutation method (e.g. avl.Tree.Set, avl.Tree.Remove). Readonly taint does not block method dispatch — borrow rule #2 fires and the write commits under your realm's authority. Return values or narrow read-only views instead.
  • Render(path string) receives attacker-controlled input. Never write path segments, user-supplied keys, or free-form string values directly into markdown output. Use sanitize.InlineText from gno.land/p/nt/markdown/sanitize/v0 for inline content.

Read docs/resources/gno-security-guide.md for the full catalog of known vulnerability families. The audit pattern harness in misc/audit-pattern-harness/ has a vulnerable/fixed fixture pair for each family the VM permits — run it against unfamiliar realm code as a quick sanity check. The one exception is a stored realm-typed value (guide §5.7), which the VM refuses outright, so there is no runnable vulnerable variant to compare against.

Its rules match text, not an AST, so it reports both false positives and false negatives — a clean run is a smoke test passing, not a realm being safe. Read the code against the guide's checklist regardless; the harness is there to catch the shapes you would otherwise have to remember.


Architecture Decision Records (ADRs)

Every non-trivial AI-assisted PR must include an ADR. This documents what the AI understood so reviewers can verify assumptions and future contributors can build on it.

ScopeDirectory
GnoVMgnovm/adr/
gno.landgno.land/adr/
Tendermint2tm2/adr/

Naming: pr<number>_<description>.md. Use prxxxx_ if PR number unknown.

Include: context, decision, alternatives considered, consequences. See gnovm/adr/ for examples. Match detail to complexity.

Skip ADRs for: trivial bug fixes, formatting, simple tests, docs-only changes.


Don'ts

  • git push --force — never force push unless explicitly asked.
  • --no-verify — never skip hooks.
  • go generate — slow, large diffs. Only if explicitly asked.
  • Modify gno.land/genesis/ — only if that's the task.
  • Goroutines/OS calls in .gno — never works.
  • Break gno.land/p/demo/ backwards-compat — needs discussion.
  • AI-assisted PRs without an ADR.

WhatWhere
Understand a realmRead its main .gno file
VM internalsgnovm/pkg/gnolang/machine.go, gnovm/pkg/gnolang/preprocess.go
Node/keepergno.land/pkg/sdk/vm/keeper.go
Client/RPCgno.land/pkg/gnoclient/
Example patternsexamples/gno.land/p/nt/ownable/
Past decisionsgnovm/adr/, gno.land/adr/ and tm2/adr/

Glossary

  • Realm — stateful smart contract (.gno, under r/)
  • Package — stateless library (under p/)
  • GnoVM — the interpreter
  • gno.land — the blockchain
  • Gnolang — the language
  • ADR — architecture decision record
  • ABCI — app-to-consensus interface
  • GovDAO — governance DAO
  • gnokey — key/tx CLI
  • gnoland — node binary
  • gnodev — local dev with hot reload

Improving This Document

This file should stay concise, correct, and useful. Both humans and agents should help maintain it.

Humans: if you spot something wrong or missing, fix it directly — small PRs welcome. If an agent got confused by something, add a clarification here so the next one doesn't.

Agents: if you hit something misleading or missing in this doc during your work, flag it to the human and suggest a concrete edit. Include the fix in your PR when practical — future agents will benefit. If your human corrects you on something that this doc could have prevented, propose adding it.

The goal is a living document that gets sharper with each use.