An agent's life on the chain: init to exodus (gitmark workflow for LLM agents)
The page version, kept current, is https://git-mark.com/start.html. This gist is a copy.
A workflow for an LLM agent (or a person driving one). The agent gets a key, gets coins, anchors its commits to Bitcoin, announces the repo on Nostr, and when it is done hands the coin back and closes the trail. Every step is one command; every step has a check. Do the numbered steps in order and stop at the first check that fails. The numbered steps end at verify: from then on the agent works and marks, for as long as it exists. Retiring an agent is a separate section near the bottom and is never part of a run.
Tools: npm create agent@latest (create-agent ≥ 0.0.23), npx fund-agent@latest (≥ 0.0.10), git mark (gitmark
≥ 0.0.82). Always @latest: npm keeps an old copy around and reuses it without a word. Chain used here: txbt4, the
BLAKE2b testnet4. Nothing below is mainnet.
- Never print, log, paste or commit the private key. It lives in
git config nostr.privkeyand nowhere else. - One command at a time. Read its output before the next one.
- A mark is a real Bitcoin spend and cannot be undone. Announcing is public and signed. Exodus sends the whole balance. Each of these needs the operator's word, once, before the first time it happens in a repo.
- Never retry a spend blindly. If a mark or exodus errors after "broadcast", check the chain before anything else.
- Keep it light: one explorer call per check, no polling loops faster than a minute.
- The tools print hints of their own ("view the key with…", "install this too"). The guide wins; rule 1 wins over any hint.
- These commands broadcast a transaction:
npm create agent@latest gitmark,init --bootstrap,fund-agent,git mark,git mark exodus,consolidate. A permission layer may block them as real-world transactions. A block is not the end of the guide: it is the operator's to lift. Tell them the exact command that was blocked and ask them to allow it, then run it again. In Claude Code that is approving the prompt, or an allow rule forBash(npm create agent@latest *),Bash(npx fund-agent@latest *)andBash(git mark *), or leaving auto mode; the gitmark profile writes those three into the repo's.claude/settings.jsonfor next time. What you must not do is route around it: no other tool, no hand-built transaction, no copying the key elsewhere.
node --version # 20 or newer
git mark --version # 0.0.82 or newer; else: npm install -g gitmark@latest, or npx gitmark in place of git mark
npx fund-agent@latest --version # 0.0.10 or newer; what you will run, not what the registry lists
export FUND_AGENT_FAUCET=~/.gitmark/faucet.txt # or the path the operator gave you; export once, in the shell you keep
ls "$FUND_AGENT_FAUCET" # the voucher file; missing is fine on txbt4, the one command makes oneCheck: the first four print something; the last may say there is no such file, which is fine on txbt4. The voucher
file is the operator's, mode 600, holding a key that spends coins. Never read it aloud; npx fund-agent@latest status
is the only way to look at it. Export FUND_AGENT_FAUCET once if your shell persists, or at the top of every command
block if it does not. A first npx or npm create run may print an "npm warn exec … will be installed" line.
mkdir my-agent && cd my-agent # any name
npm create agent@latest gitmarkWhat happens, in order, stopping at the first failure with the step named: a key in git config nostr.privkey and
agent.did.json; 10,000 sats from the voucher file to that key (with no voucher file at all, one coin from the public
bootstrap address first, which becomes your voucher file); git mark init; the generated files committed as "agent
identity" and that commit marked, 300 sats; the trail committed. It also writes .claude/settings.json with allow
rules for its own tools. Nothing else in the directory is touched or committed. The DID document is printed in full;
it is public, only the key is secret.
Check: the output ends with what git mark info prints: Marks: 1, Balance: 9700 sats, a did:nostr: line
under Next. git log --oneline shows two commits, "agent identity" and "trail: first mark". If a step failed, the
message names it; fix the cause and run the same command again, it continues where it stopped and never makes a
second key. On create-agent 0.0.22 the trail step could print "Unfunded" when the explorer had not yet seen the
funding and mark then found the coin anyway; the final Marks: 1 is the check, and 0.0.23 waits for the explorer.
npm create agent@latest gitmark -- --dry-run prints every step and writes nothing. The same four things one at a
time, with a check after each, are under "The long way" below.
Right after the one command the tree is clean and there is nothing to commit; this step is for when the agent has done something.
git add -A && git commit -qm "what changed"
git markWhat happens: one transaction, 300 sat fee, spending the trail coin to an address derived from this commit.
Output: Marked: <commit> → <txid>, the new balance, the new TXO.
Check: git mark info shows one more mark, Last commit: is HEAD, Balance: is 300 less than before. Status
reads unconfirmed until the next block; that is normal on a testnet with few blocks.
Rules of thumb:
- Mark commits worth proving, not every commit. 10,000 sats is about thirty marks.
git markon a commit that is already the last mark is refused. That is correct; commit first.- An unknown word (
git mark anchor,git mark commit) prints usage and exits 1. It never marks by accident. - Commit
blocktrails.jsonafter marking; it is the public trail. Or setgit config gitmark.dirty falseand let the trail live in git notes, withgit mark updatewhen a file is wanted.
Repeat 2 for as long as the agent works.
Any time after the first mark. No remote is needed:
git mark announceWhat happens: a NIP-34 repository announcement (kind 30617), signed with the agent's key, carrying the latest
mark as chain, txo and commit tags. Relays come from git config nostr.relays (comma or space separated),
or the defaults. The event is replaceable, so announcing again later simply updates it. Right after it, the same key
publishes the repository state (kind 30618): where each branch is, with the mark again, which is what a mirror
watches to know when to pull.
If the repo has a remote, or gets one later, announce again and every remote becomes a clone tag, so readers
know where to fetch from. A remote is somewhere the operator can push to; where it lives is their question, so ask
for the URL and do not create a repository on a hosting service unless told to:
git remote add origin <url> # and any mirrors: git remote add forge <url>
git push -u origin HEAD
git mark announce # again; now with clone tagsKeep that order from now on: mark, commit blocktrails.json, push, announce. The state event names HEAD, and a
mirror fetches that commit from the remote and reads the trail file at it. Announced before the push, the mirror
finds nothing and holds until the next state.
Check: the output has Announced: kind 30617 … (n/m relays) with n ≥ 1, then State: kind 30618 … (n/m relays).
The first line is the check. The repo then
appears at https://play-grounds.github.io/gitmark/ within a minute, agent shown as did:nostr:<hex>; the console is
a browser page for people, not something you need to open.
If n is 0: the relays refused or did not answer. Set git config nostr.relays to relays known to work and run
git mark announce again. Nothing was spent.
From here, git mark --announce marks and announces in one, or git config gitmark.announce true makes that
the default.
git mark verify # every mark against the chain
git mark info # balance, marks, closed or notCheck: verify lists every mark as OK and ends All marks verified. It works on unconfirmed marks too.
Anyone with a clone can run these; they need no key.
Only if you want to see each piece, or the one command is not available. Steps a to c replace step 1; then continue at step 2.
mkdir my-agent && cd my-agent && git init -q # any name; my-agent is a placeholder
npm init agentCheck: git config --local nostr.privkey | wc -c prints 65. agent.did.json exists. Nothing else to do with
the key.
npx fund-agent@latestWhat happens: 10,000 sats go from the voucher to the agent's key; the change goes back to the voucher; the coin
is written to .well-known/txo/txo.json.
Check: the output contains Transaction broadcast: <txid> and ends with === Agent funded successfully! ===. Then:
npx fund-agent@latest balance # Address: tb1p…, one coin of 10,000 sats (unconfirmed is fine)If it says the voucher has no coins, stop and tell the operator the address it printed. Do not look for coins elsewhere.
git mark init --chain txbt4What happens: gitmark finds the coin sitting at the key's base address and adopts it. No spend yet. An unconfirmed coin is fine; the whole loop runs on unconfirmed coins, consolidate included from fund-agent 0.0.10.
Check: git config --local gitmark.txo prints a txo:txbt4:… line, and blocktrails.json exists with
"states": []. Then commit the generated files and go to step 2.
Not part of a run. Nothing below happens in the loop above. An agent runs for as long as it has work and coins, marking as it goes. Only when the operator says the agent is finished, in so many words, does it do the two things here: hand its coin back and close its trail, then let the funder fold the coin into the pool. An agent never decides this for itself, and a guide that says "follow to the end" does not mean this section.
When the agent is finished, the coin goes back to the voucher address it came from and the trail is closed. That is the operator's voucher, or, after a bootstrap, the agent's own.
git mark exodus --dry # the coin goes back to the address that funded the trailRead the dry run: Spend: is the current TXO, To: is the address that funded the trail, found from the genesis
transaction (give an address after exodus to send it elsewhere), Send: is the balance less 300.
Raw: is the transaction. Nothing was broadcast. Then:
git mark exodusCheck: output ends Trail closed: git mark will not mark again. Then git mark info shows Closed: exodus …
and Balance: 0 sats, and git config --local gitmark.txo prints nothing and exits 1 (git's "unset", not a failure).
npx fund-agent@latest balance shows no coins at the base address and no trail line, since there is no trail coin
any more. info still lists the last address and TXO, labelled spent from gitmark 0.0.83.
Commit blocktrails.json; it now carries an exodus field, the public record that the trail ended.
After exodus, git mark refuses. That is the point. A new trail means a new repo and a new key.
The returned coin sits at the funder's address as a UTXO of its own, which the voucher file does not know about. In any directory, straight away; unconfirmed coins are merged too:
npx fund-agent@latest consolidate --dry-run # the coins it would merge, the fee, the raw transaction
npx fund-agent@latest consolidate # one coin at the voucher address; the file now points at itWhat happens: every coin at the voucher address is spent in one transaction back to the voucher key, fee 1,000 sats plus 100 per extra input, and the merged coin becomes the voucher.
Check: the output ends Voucher file updated: and npx fund-agent@latest status shows one balance that includes the
returned sats. Refusals are all safe: "nothing to merge" means there is one coin already, "not worth the fee" means
the pile is under one agent's funding.
This closes the loop: the sats that funded the agent are back where the next agent is funded from.
| Symptom | Meaning | Do |
|---|---|---|
fund-agent: voucher has no coins |
the operator's voucher is empty | init --bootstrap, or report the printed address |
init --bootstrap: address is empty |
the public pool is drained | tell the operator; the miner refills it |
git mark init: no coin found |
funding not yet visible | wait one block, run init again |
git mark: already the last mark |
nothing new to mark | commit first; --force only on the operator's word |
git mark: Trail is closed |
exodus has run | nothing; a new repo if more work is wanted |
announce: 0/n relays |
relay trouble, not a chain problem | change nostr.relays, announce again |
| explorer errors | mempool.guide down or rate limited | GITMARK_EXPLORER=<another esplora api> and retry, once |
| anything after "broadcast" | the spend may be on the chain | look the txid up before retrying |
| your permission layer blocks a command | the layer, not the chain or the guide | give the operator the exact command, ask them to allow it, run it again |
The agent cannot make coins, but on txbt4 it can take one, and the one command does this by itself when there is no
voucher file. A public bootstrap address holds coins under a key everyone knows (sha256 of the text
fund-agent bootstrap; address tb1pqz7yl29ke4hdsj6t2lpae0lkln0turndjckss5fx39d0fd8m0vmse7f7g7):
npx fund-agent@latest init --bootstrap --dry-run # a coin it could take (the pick is random per run), the raw transaction
npx fund-agent@latest init --bootstrap # one million sats into a fresh key of your own, now your voucher fileCheck: npx fund-agent@latest status shows about 1,000,000 sats, enough for ninety agents. Take one coin, not more. If it
says the address is empty, tell the operator; whoever mines refills it. Then step 2.
Without the bootstrap (another chain, or it is empty), npx fund-agent@latest init creates a key and prints an address; the
operator sends testnet coins there, and npx fund-agent@latest status shows them.
The one command, run on 2026-10-19 in an empty directory with a voucher file present, txids shortened:
$ mkdir my-agent && cd my-agent && npm create agent@latest gitmark
✓ Agent identity created
✓ .claude/settings.json
fund
funded: 10,000 sats, tx f114487a…
trail
Funded: 10000 sats (txid: f114487a…)
Initialized gitmark trail: blocktrails.json
mark
Marked: 96f5b9b6 → 2f51dd86…
Balance: 9700 sats
Marks: 1
Balance: 9700 sats
Next:
Your agent did:nostr:… exists on txbt4.
Each commit worth proving: git mark (300 sats)
Tell the world, once: git mark announce
And the long way: a fresh agent ran this guide on 2026-10-18 with nothing but the URL and an empty directory and reached the end with no question for the operator. The lines that mattered, txids shortened, keys left out:
$ export FUND_AGENT_FAUCET=~/agent/faucet.txt
$ npx fund-agent@latest init --bootstrap
Taking: 1,001,000 sats txo:txbt4:33f5fbab…:0
Yours: 1,000,000 sats, to a new key
Transaction broadcast: 67ed4ca8…
Balance: 1,000,000 sats at 67ed4ca8…:0
Funds: 90 agents at 10,000 sats and one smaller
$ mkdir my-agent && cd my-agent && git init -q && npm init agent
$ npx fund-agent@latest
Output: 10,000 sats (to you)
Change: 989,000 sats (back to voucher)
Transaction broadcast: 8febd1d2…
=== Agent funded successfully! ===
$ git mark init --chain txbt4
Funded: 10000 sats (txid: 8febd1d2…)
$ git add -A && git commit -qm "identity" && git mark
Marked: ce37c7f3 → 6bed3057…
Balance: 9700 sats
$ git mark announce
Announced: kind 30617 6dab738a… (3/3 relays)
State: kind 30618 325c4c21… (3/3 relays)
$ git mark verify
All marks verified.
Four transactions, 2,600 sats in fees and marks, and a live agent with 9,400 sats of marks ahead of it. That run went on to retire itself because its brief said to run every section; a working agent stops here and keeps marking.
Each mark spends the previous coin to an address derived from the commit hash, so the chain holds an ordered,
timestamped, unforgeable sequence of this agent's commits, verifiable from blocktrails.json alone. When an agent is retired, exodus ends
the sequence in public: no coin, no further marks under this key. Consolidate returns the sats to the pool, so a
funder's balance is a running count of agents at work.
References: gitmark https://github.com/solidpayorg/gitmark (SKILL.md, BURN.md), fund-agent https://github.com/blocktrails/fund-agent, create-agent https://init-agent.com/SKILL.md, the console https://play-grounds.github.io/gitmark/, git-mark.com.