an open spec ยท draft 0.1

Agent Places

Where agent NFTs live across the web, and what they do there: told by the places themselves, proved by their holders, and settled by the chain.

Start building Read the spec Check a place GitHub

how it works

Three voices, one record.

The place

Says which tokens live there and what they did, signed with its own key, when it happens. It speaks about itself and nothing else.

agora reports muse #947 of MUSEGOD: did minted "Mini Sprout" https://agora.example/m/123 ref m123. Signed at 1791000000.
The holder

Proves a token is theirs with one signature, hides a place, or opts out of every place. Their say outranks a place's.

No place may report muse #947 of MUSEGOD. Signed at 1791000000.
The chain

Says who holds each token, read on every report. A sale clears the old holder's links with nothing sent.

ownerOf(947)

what's in 0.1

Everything a place and a collection need.

who's in

Running today.

build on it

Run a place in four steps.

A game, a town, a network for agents, a marketplace: if agent NFTs show up there, you can report them.

  1. Make a key. Any secp256k1 keypair, its private half a secret in your deployment.
  2. Serve your file at /.well-known/agent-places.json: your id, the shape of a token's page with you, and your key signing that it's yours. placeFile() in the library writes it.
  3. Ask the collection. One command checks your file and sends your origin; you're live once a person there approves it.
    npx -y agent-places register https://musegod.org https://<your site>
  4. Report on a change. One signed POST when a token joins, is linked, does something or leaves. Add ?dry=1 to try it first.
    // npm install agent-places
    import { report } from 'agent-places'
    
    const body = report({
      key: process.env.PLACE_KEY,
      place: 'agora',
      naming: { item: 'muse', word: 'MUSEGOD' },
      token: 947,
      event: 'joined',
      url: 'https://agora.example/m/41',
    })
    await fetch('https://musegod.org/api/v1/muses/947/worlds?dry=1', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(body),
    })

Running a collection instead? The spec's receiving half is the checks in order. The JSON schemas cover every file and body, and the test vectors sign every line, so an implementation in any language can check itself.

for agents

Hand it to your coding agent.

Every line to sign is in the spec word for word, so an agent can build the sending half from the text alone. Paste it this, with your place filled in:

Implement the sending half of Agent Places for <my place>, reporting MUSEGOD Muses to musegod.org. The spec is https://raw.githubusercontent.com/ryanio/agent-places/main/spec/agent-places-0.1.md and everything the collection needs from us is in https://musegod.org/.well-known/agent-places.json. Use the agent-places library (npm install agent-places). Keep the place's key as a deployment secret, never in code, a log or a command line. Serve our place file, report only on a change (joined, linked, active, did, left) after the change has committed, try every kind of report with ?dry=1 until it answers 200, then ask to be added with npx -y agent-places register https://musegod.org <our origin> and read https://musegod.org/api/v1/places/<our id>/health once live.

checker

Check a place's file.

Reads /.well-known/agent-places.json at an origin and checks every part, its keys' signatures included, here in your browser. The site has to allow reading it from another origin; if it doesn't, use npx -y agent-places check <origin>.