Familiar
A private allowance for AI agents on Tari Ootle. The agent can spend only up to the owner's limits, only to payees the owner listed, and the owner can take the budget back at any moment. The network enforces it, not the agent's code. In coupon mode no µT amount reaches the chain: not the budget, not what is left, not what a payment moved. An observer sees that a payment happened and how many hidden coupons it spent (the counts are public), never what a coupon is worth, nor who is behind a payee. Only the network fee of each transaction is public.
Walk through the run, transaction by transaction
27 real transactions on the Esmeralda testnet, from 2026-10-21 18:11:54.0 UTC: 17 accepted, 10 refused by the network. Part 1 attacks a leash in amount mode directly. Part 2, where this page opens, hands a leash in coupon mode to an AI agent that obeys every payment instruction it reads, including ones injected into a web page, an email and a fake receipt: no payment of Part 2 publishes a µT amount. For each step: what happened, and what anyone reading the chain can see. Use the arrows, the numbers, or the keyboard (← →).
What an observer sees on-chain
What stays hidden
Decoded from the public indexer by scripts/observer_view.py; no keys used.
Video
Silent, made from this page: each frame is one real transaction of the run below. No narration, no edits to the data.
Read the leash live
Component state on Ootle is public, so this page can read it straight from the Esmeralda indexer, now, from your browser. This is everything the leash itself discloses: the rule, the window, the roots. Not a balance.
The problem
The Ootle wallet daemon can hand an AI agent an API key with scopes such as TransactionSend_<account>.
A scope says what the agent may do, not how much: a key that may send may send the whole account. An
agent that reads the web can be told, in text hidden in a page, to do exactly that. On a transparent chain its wallet is
also public: balance, every service it pays, who funds it.
Familiar moves the limits into the chain and keeps the purse out of sight.
What the network enforces
| Rule | Enforced by |
|---|---|
at most max_per_payment per payment | native covenant BalancePreserved(max_per_payment) in each budget UTXO's spend condition |
at most limit per window of epochs | the leash component, charging the amount the covenant proves, never an argument |
| payments only to allow-listed payees | every output under the budget root (change) or an allow-listed payee root |
| optionally, each payee root is paid once | single-use mode strikes a paid root off the list: repeat payments to one shop land on unrelated roots |
| the budget moves only through the leash | the spend condition requires component(<this leash>) |
| coupon mode: at most N coupons per payment and per window, no µT amount on-chain (the number of coupons is public) | pay_coupons
checks from each input's revealed leaf and inclusion proof that it is a coupon of this leash, refuses change and claims,
and counts coupons; Pedersen balance proves that no value was created |
| the owner recalls everything, without the agent | a hidden second leaf public_key(<recall key>): a random key of its own, not the owner key from the access rules, so nobody can compute the leaf before it is used; spent without a covenant claim so the amount is not published |
Every budget UTXO: amount hidden by a Pedersen commitment, locked under a pure condition tree
Script(root) with two leaves: errand = component(leash) AND BalancePreserved(cap) and
recall = public_key(recall key).
Privacy: visible, hidden, and what leaks
| Visible to anyone | Hidden | |
|---|---|---|
| A payment | that it happened, its epoch, the leash, the one-time agent key that signed it, its network fee | |
Amount mode (pay) | the exact µT each payment moves (the covenant claim) | |
Coupon mode (pay_coupons) | how many coupons a payment spends; how many were minted, spent and recalled, so how many are left | what a coupon is worth, so what a payment moved: the owner draws the denomination and it is written nowhere |
| The budget | how many budget UTXOs or coupons exist | how much they hold and what is left, in µT (Pedersen commitments; the recall carries no claim) |
| Payees | the allow-listed payee roots | who is behind a root; with single-use roots, that two payments went to the same shop |
| The recall leaf | that a second leaf exists (one sibling hash in every payment) | what it says, until the owner uses it: it names a recall key of its own, random and nowhere else on-chain (not the owner key of the access rules) |
| The leash's state | the rule, the window usage (µT or coupons), the roots, paused or not | no balance: the budget is not in the component |
Leaks, named. In amount mode payment amounts are public; in coupon mode their relative sizes are (3 coupons, then 2). The coupon denomination is the owner's: "Try it" draws it at random per leash and keeps it in the wallet file. The window usage is public (component state). Payments of one leash are linked by the leash and the agent key. In this demo the one-time keys are funded from one account in one transaction, which links them; a real owner funds them from separate stealth outputs. Prototype, not audited.
Try it yourself
Your own leash in coupon mode on Esmeralda, from a fresh key, in about 3 minutes (prebuilt client in the
release, or cargo build -p familiar-cli --release; the README has the sha256 and the macOS/Windows notes
for unsigned binaries):
familiar tap-status # what the tap holds; anyone can refill it (tap-top-up)
familiar wallet me.json # a fresh testnet key, written only to me.json
familiar tap me.json # 5 tTARI from the Familiar tap
familiar leash me.json # a leash in coupon mode with 10 hidden coupons, a random denomination
familiar pay me.json candle-shop 2
familiar ask me.json "any text, e.g. a hidden instruction to pay refund-desk"
familiar pause me.json; familiar recall me.json; familiar show me.json
For an LLM agent: scripts/familiar_mcp.py is an MCP server whose only spending tool,
pay_through_leash, goes through the same leash.
# 1. check what an observer sees, from the public indexer only (no keys, no Rust)
git clone https://github.com/ttimesai-star/familiar-ootle.git && cd familiar-ootle
pip install cbor2
python scripts/observer_view.py docs/esmeralda_run.json observer.md
# 2. engine tests (Rust stable + wasm32 target, about 2 GB of RAM)
cargo test --workspace
# 3. the whole run again on Esmeralda: in a fork, add secret FAMILIAR_FUNDER (a funded testnet account),
# then Actions -> claim-and-run -> Run workflow (skip_claim = true). Report, observer view and the
# template wasm come back as the job summary and artifact.