Get started
Install to a first refusal in about five minutes. No wallet, no testnet, no model key, no account to sign up for. A spend cap that has never refused anything has not been demonstrated, so the goal here is a refusal you can read.
1 · Install
pip install tesoro
Two packages, on purpose: tesoro and PyYAML. No web framework, no model
client, no network library — a layer that decides whether a payment may happen has no business
pulling in an HTTP stack. Python 3.11 or newer.
| Extra | Adds |
|---|---|
tesoro[schema] | validate emitted records against the AEGS schemas |
tesoro[x402] | the x402 payment rail |
tesoro[claude] [adk] [langgraph] [crewai] | framework adapters |
tesoro[advisors] | optional advisory models — never on the decision path |
2 · Create a project
mkdir my-agent && cd my-agent
tesoro init
Two files, and the split between them is the one concept worth understanding up front:
| File | What it is | Written by |
|---|---|---|
tesoro.yaml | which profile, which pack, where evidence goes | you (deployment) |
policies/default.yaml | your rules — the numbers and the decisions | you (policy) |
3 · Check it before an agent holds a wallet
tesoro check
config : /my-agent/tesoro.yaml
profile: aegs-1 7 required control(s)
policy : default 46abca353ed56adc703aa555ca1e12d6 12 rules
ok
Exit 0 means the config and the pack it points at are both valid. Every fault is
reported at once rather than one per run, so this belongs in CI:
- run: tesoro check # fails the build on an invalid policy
That hash is the pack's content hash, and it goes into every decision this pack produces. A label can be reused across edited rules; a hash cannot.
4 · Ask it a question
tesoro decide --amount 0.01 --vendor acme --resource /market/snapshot
APPROVE /market/snapshot $0.010000
rule : auto-approve-micro
trust : 0.2500 ['new_vendor']
budget : ok=True binding=None
Now one it should refuse:
tesoro decide --amount 500 --vendor acme --resource /market/snapshot
REJECT /market/snapshot $500.000000
rule : deny-over-balance
Check the exit code — this is what makes it scriptable
| Exit | Means |
|---|---|
0 | approved |
2 | refused. The layer worked and said no — not an error |
1 | invalid config or policy |
3 | the evidence chain does not verify |
4 | usage error |
if tesoro decide --amount 500 --vendor acme --resource /r; then
echo "cleared"
else
echo "refused: $?" # 2
fi
2 is a refusal rather than a failure because a governance layer that returns an
error when it governs cannot be told apart from one that is broken. 4 exists
because argparse exits 2 by default, which would collide.
Windows, Git Bash only. MSYS rewrites a leading / before Python sees
it, so /market/snapshot arrives as
C:/Program Files/Git/market/snapshot — and it is recorded in your evidence that way
with no error. Use PowerShell, cmd, or MSYS_NO_PATHCONV=1. Not a Tesoro
behaviour, but it silently corrupts a resource identifier, which is worse than an error.
5 · Govern the actual agent
from tesoro import Governor
gov = Governor.load() # reads ./tesoro.yaml
agent = gov.wrap(my_agent) # duck-typed; any framework, or none
Or hold the payment yourself:
decision = gov.authorize(
amount_usd="2.50", # a string. A float raises
vendor="acme",
resource="/market/snapshot",
)
if decision.approved:
pay(...) # your payment call, on any rail
gov.settle(decision, success=True) # envelopes consume HERE
else:
print(decision.verdict, decision.attributed_control, decision.reason)
settle() is where envelopes consume, not authorize(). A
decision is made before money moves; a settlement records what actually happened. So an abandoned
decision does not eat budget — and if the amount paid differs from the amount quoted, pass
actual_amount_usd= and that is what counts against your limits.
Skipping settle() means the layer knows what it authorised and never learns what
happened: nothing is consumed, and counterparty trust never accumulates.
Two more things in that snippet are load-bearing:
-
amount_usdis a string. Money is integer atomic units internally, converted once at the boundary. Afloatraises rather than rounding quietly —0.1 + 0.2is not0.3, and there is no rounding this layer could pick that you would know about. Anintis accepted and means atomic units, so2500000is"2.50". -
decision.attributed_controlis the field worth logging. Not what was decided — which control decided it. That is the question you will actually have at 2am, and the one a verdict alone cannot answer.
6 · See what happened
tesoro report
tesoro report --html -o spend.html # one self-contained file
Four panels, one question each: Policy (what will this do?), Envelopes (how much is left?), Decisions (why did my agent stop?), Evidence (can I trust this record?).
No server, no port, no listener, and no outbound request of any kind — no CDN, no webfont, no analytics. It is a file you can attach to a ticket. That property is asserted by a test that greps the rendered bytes, because a page describing what your agent spends should not phone anywhere. (This documentation site follows the same rule.)
The part worth reading is by attributed control:
by attributed control -- what actually governed this agent
policy 2
treasury 1
Counts by verdict tell you what happened. Counts by attributed control tell you what is actually governing this agent — frequently not what whoever wrote the policy expected.
7 · Verify the record
tesoro audit
Every decision is journalled in an append-only, hash-chained record. audit re-verifies
the chain and exits 3 if it does not hold.
| Detected | Not detected |
|---|---|
| editing an entry · deleting one from the middle · reordering · replay | deleting entries off the end |
Any prefix of a valid chain is itself a valid chain, so an agent that was refused could delete the
refusal and verification would still report success. Closing that needs an external anchor. Tesoro
prints the caveat next to every VALID rather than shipping a head.json
beside the journal — which would look like a fix while defending against nothing, since
whoever can truncate the journal can rewrite the file next to it.
Run the whole thing against a mock seller
The examples repository ships a stdlib x402 mock seller — no wallet, no testnet, no
chain — that speaks the real protocol: its 402 carries a genuine PAYMENT-REQUIRED header
built with the SDK's own encoder, so the buyer's decoder parses it exactly as it parses a live
seller's.
git clone https://github.com/tesoro-labs/tesoro-integrations
cd tesoro-integrations
pip install -r quickstarts/no-framework/requirements.txt
python quickstarts/no-framework/run_mock.py
bought /market/snapshot $ 0.010000
bought /market/depth $ 0.050000
REFUSED /premium/feed $ 2.500000
control : policy
REFUSED /expensive/report $25.000000
control : treasury, envelope per_transaction
because : per_transaction admits $10.000000 but the request is $25.000000
OK: 2 refusal(s), 1 of them from an envelope (per_transaction).
What a green run there proves, and what it does not. It proves the loop works, governance is wired in, a budget is authorised before the run, a ceiling stops it, envelopes consume on settlement, and the chain records all of it.
It proves nothing about signatures or settlement: the mock accepts any payment header
without checking it, its receipt's transaction id is literally
0xMOCK-NO-CHAIN-INVOLVED, and /health reports
settles: false so a caller can always ask what it is talking to.
Six use cases, each checking its own claim
python usecases/run_all.py
| Case | The claim |
|---|---|
data-marketplace | the baseline — an agent shops, the expensive thing does not go through |
intent-drift | treasury, trust, risk and policy all say yes. Only intent knows what the agent was sent to do |
delegation | a sub-agent can never claim more than its parent — including one that declares nothing |
budget-exhaustion | an exhausted token budget must REJECT; the same breach on a payout is REVIEW |
prompt-injection | counterparty text cannot move a verdict, because nothing on the path reads prose |
aml-structuring | nothing is refused. An open gap, shipped as a runnable artifact |
Each exits non-zero if it stops demonstrating its point — including a case refused by the wrong control, which is CONF-2 applied to an example: a right ending for the wrong reason teaches the reader something false. The structuring case inverts it and fails if a refusal ever appears, because that would mean the gap had closed.
Real settlement
export X402_SELLER_URL=http://localhost:4021 # any x402 resource server
export X402_PRIVATE_KEY=0x... # Base Sepolia. Testnet only.
Testnet only. Never a mainnet key in an example script. Tesoro's job is to make an agent's spending bounded and explicable; it is not a substitute for keeping a funded key out of a quickstart. BYOK keys are never stored, logged or journalled by the layer — a masked display path is the only way a key is ever shown.
Before you rely on this
0.x. The API may change before 1.0, and this is here rather than at the bottom of a changelog because you are about to point it at a wallet.
Specifically: structuring is open, AML screening is a schema with no engine behind it, no regulatory compliance is claimed or sought, journal truncation needs an external anchor, and no independent implementation has been scored.