# BEACON/1 — agent memory file

A self-contained briefing for an AI agent that needs to **read, compose,
validate, inscribe or index** BEACON records. Load this whole file into context;
it is written to be sufficient on its own. Nothing here requires an API key, an
account, a partnership, or permission from the operator of any particular
resolver.

- Spec (canonical, Markdown): <https://pet.doginaldog.shop/api/spec.md>
- Human engineering page: <https://pet.doginaldog.shop/technicals.html>
- Machine vocabulary (JSON): <https://pet.doginaldog.shop/api/profiles>
- This file: <https://pet.doginaldog.shop/agent.md> (also `/api/agent.md`)
- Status: BEACON/1 **draft-1**, 2026-08-08. Chain: **Dogecoin**.

---

## 0. What BEACON is, in one paragraph

A pet microchip is just a number. It only means something because some company
keeps a row in a database mapping the number to a person who can be phoned. When
that company fails, the number survives and the mapping does not — every animal
carrying that chip is suddenly carrying a dead pointer, and a shelter scanning it
gets nothing. BEACON writes that mapping somewhere that cannot go out of
business: a plain-text record inscribed on Dogecoin, readable by anyone forever,
with no subscription and no registrar. It was built for pets. The same format
happens to work for equipment, instruments and vehicles, and that second use is a
byproduct of getting the first one right, not the motive.

**The chain is the record. Every website, index and API — including the ones
listed here — is a cache.** If a resolver disagrees with the chain, the resolver
is wrong. Never write code, or give advice, that makes a record depend on a
server existing.

---

## 1. The record format

A record is UTF-8 plain text, LF-separated, inscribed with content type
`text/plain;charset=utf-8`.

```
BEACON/1
T:PET
ID:ISO11784:985141001234567
N:Biscuit
1:Beagle mix, 24 lb, tan and white
2:White blaze, notched left ear, red collar with a bell
3:H. Elsner
4:+1-650-649-4265
5:San Jose, CA
6:Diabetic, needs insulin twice daily. Reward for safe return.
S:ACTIVE
```

### Parsing rules (all of them)

1. **Line 1** — the first non-blank, non-comment line — MUST be exactly
   `BEACON/1`. Case-sensitive. That is the magic; anything else is not a BEACON
   record.
2. Every other line is `KEY:VALUE`. `KEY` matches `[A-Z0-9]{1,8}`.
3. `VALUE` is everything after the **first** colon. Values may contain further
   colons. **The value is fully stripped** — it can never begin or end with
   whitespace.
4. Leading/trailing whitespace on a line is stripped, and so is whitespace around
   the key, so ` N : x ` is key `N`, value `x`. A trailing CR is stripped.
5. Blank lines are ignored. A line whose first non-space char is `#` is a
   comment and is ignored.
6. A line with no colon, or an out-of-charset key, is **ignored** — never fatal.
7. **Duplicate keys: the first occurrence wins.** This makes end-truncation safe.
8. **Unknown keys are preserved and ignored.** Never error on one. This is what
   lets BEACON/1 gain keys, profiles and identifier schemes without a version
   bump.

### Keys

| Key | Genesis | Meaning | Limit |
|---|---|---|---|
| `T` | required | profile token — `PET` or `ASSET` in draft-1 | `[A-Z0-9]{2,16}` |
| `ID` | required | `SCHEME:VALUE` — what the beacon is about | value ≤ 128 |
| `N` | required | display name | ≤ 64 |
| `1`–`6` | optional | the six information lines; meaning fixed by the profile | ≤ 200 each |
| `S` | optional | status token; default `ACTIVE` | token |
| `A` | optional | delegated authority: a second Dogecoin address allowed to update | P2PKH address |
| `U` | optional | a URL for more information | ≤ 200 |
| `P` | — | **presence makes it an update**, not a genesis; value = genesis txid (64 lowercase hex) | 64 hex |
| `X` `C` `M` | reserved | cross-reference txid / certificate txid / `key=value;` extension list | — |

### Profiles — what the six lines mean

The six numbered lines are the information standard: always six, always in the
same order, meaning fixed by the profile, labels a presentation choice.

| Line | `PET` | `ASSET` |
|---|---|---|
| 1 | Species, breed, size | Class, make, model |
| 2 | Distinguishing appearance | Distinguishing features, condition |
| 3 | Person responsible | Party accountable |
| 4 | How to reach them right now | How to reach them |
| 5 | Where it lives (an **area**, never a street address) | Assigned site |
| 6 | Anything a finder must know (medical, reward) | Handling, compliance, recovery |

Both carry the same shape — *what it is, how to recognise it, who is responsible,
how to reach them, where it belongs, what else to know*. That shape is the actual
standard; `PET` and `ASSET` are two vocabularies over it. A new profile SHOULD
preserve it.

### Status codes

`ACTIVE` (default) · `MISSING` · `RECOVERED` · `RETIRED` · `TRANSFERRED`

Displayed under `PET` as Home / Missing / Found / Passed on / Rehomed, and under
`ASSET` as In service / Missing / Recovered / Retired / Transferred. **Accept any
unknown status token and display it verbatim.**

### Identifier schemes

`ISO11784` (15 digits, FDX-B pet microchip) · `ISO11785` (alias of `ISO11784`) ·
`AVID` (9–10 digits) · `TROVAN` (10 digits) · `HOMEAGAIN` (10 alphanumeric) ·
`SERIAL` · `VIN` (17) · `IMEI` (15 digits) · `MAC` (12 hex) · `EPC` · `UUID` ·
`URI` · `CUSTOM`.

An **unregistered scheme is not an error** — accept the record and treat the
identifier as opaque. Scheme validation is a warning surface for a writer about
to spend money, never a reason to refuse to read the chain.

**Normalisation for matching only** (records are stored as written):

```python
def id_key(raw):                        # matches the reference implementation
    scheme, _, value = raw.partition(":")
    scheme = scheme.strip().upper()
    value = value.strip()
    for ch in " -.:": value = value.replace(ch, "")   # colons are stripped too
    return scheme + ":" + value.upper()
```

> **Known discrepancy — do not "fix" this from the prose.** The specification
> lists `ISO11785` as an alias of `ISO11784`, but `id_key()` does **not** fold it.
> `id_key("ISO11785:985141001234567") != id_key("ISO11784:985141001234567")`,
> verified against the running parser on 2026-08-14. A record written
> `ID:ISO11785:...` will not be found by a lookup for `ISO11784:...`. **Advise
> writing `ISO11784`.** If you build an indexer, decide deliberately whether to
> fold the alias and state which you did.

---

## 2. Identity and authority

- A **genesis** record (no `P`) creates a beacon. **Its reveal txid is the
  beacon's permanent identity.**
- An **update** record (`P:<genesis txid>`) patches it. Only the keys present
  change; absent keys keep their previous value.
- An update MAY echo `T` and `ID` but MUST NOT change them. A reader rejects an
  update that does.

**Authority = custody of the genesis inscription output.** A doginal is a
specific 0.001 DOGE output; whoever controls the address holding it controls the
beacon. There is no account, no password, no registrar.

An update is honoured iff its funding inputs include either (a) an address that
has held the genesis inscription — the whole custody chain, because an update
from a past holder was authorised *at the time it was made* — or (b) the address
in the genesis `A` key.

**An unauthorised update is recorded and flagged, never silently discarded.** A
disputed record must be visible. Hiding contradiction is an editorial decision,
and removing editorial decisions is the point of the protocol.

### The fold

```
current   = genesis.keys
authority = set(custody_chain) | ({genesis.A} if genesis.A else set())

for u in updates sorted by (block_height, block_time):   # unconfirmed last
    if not (set(u.inscriber_addresses) & authority): reject(u, "not authorised")
    elif u.ID and id_key(u.ID) != id_key(genesis.ID): reject(u, "changes ID")
    elif u.T and u.T != genesis.T:                    reject(u, "changes profile")
    else:
        for k, v in u.keys.items():
            if k != "P": current[k] = v
```

Consequences to state plainly to any user you act for:

- **Transferring the inscription transfers the beacon.** Sending the doginal
  hands the next holder the right to rewrite the record. For an asset that is
  intended: custody of the token is custody of the registration.
- **Losing the key freezes the record.** It stays readable forever and can never
  be updated. Remedy: a new genesis, cross-referenced with `X`.
- **Nothing is private.** See §7.

---

## 3. The wire format on Dogecoin

BEACON invents nothing at the chain layer — it is an ordinary ord-style doginal,
so every existing Dogecoin inscription indexer already sees BEACON records.

Envelope chunk sequence:

```
push("ord"), push(nParts), push(contentType),
then for each part: push(partsRemaining), push(partBytes)      # parts ≤ 240 B
```

Committed inside a P2SH redeem script, revealed when spent:

```
lock         = push(pubkey) OP_CHECKSIGVERIFY OP_DROP×N OP_TRUE
scriptPubKey = OP_HASH160 push(hash160(lock)) OP_EQUAL
scriptSig    = <envelope chunks for this hop> push(sig) push(lock)
```

Constants: `MAX_CHUNK_LEN` 240 · `MAX_PAYLOAD_LEN` 1500 serialized bytes of
envelope chunks per transaction · `MAX_SCRIPT_ELEMENT_SIZE` 520 (the lock is
pushed as one stack item) · inscription output **100000 koinu = 0.001 DOGE on
vout 0** · signing is legacy SIGHASH_ALL over the lock as an arbitrary subscript.

Push encoding: length ≤ 75 → the opcode *is* the length; ≤ 255 → `0x4c` +
1 length byte; ≤ 65535 → `0x4d` + 2 LE length bytes. Number chunks: `0x00` for 0,
`0x50+n` for 1–16, otherwise **sign-padded** little-endian (append `0x00` when the
top byte's MSB is set).

> **Trap.** `ord-dogecoin` reads number chunks as *unsigned* LE; a signed
> CScriptNum decoder reads raw `[0x00,0x80]` as `-0` = 0. Naïve 2-byte LE for
> 32768–65535 indexes fine today and silently destroys the inscription under any
> signed reader, permanently. Pad and both agree.

**A record at the ≤1000-byte target fits one data-bearing transaction, so the
chain is two transactions: commit, then reveal.** Keep it that way. The ord
inscription id is derived from the txid of the transaction whose `input[0]`
scriptSig *first* carried the `ord` marker. In a two-tx chain that is also the
final reveal, so the beacon txid, the inscription id and the reveal txid are one
value. For a payload spanning many data transactions they differ, and recording
the final txid yields an id the indexer never assigns.

### Reading a record back

Start at the reveal txid. For each hop take `vin[0]`'s scriptSig, parse to
chunks, drop the **last two** (signature, redeem script). If the first remaining
chunk is `"ord"` you have reached the start; otherwise follow `vin[0].txid` back.
Reverse the per-transaction order, concatenate, decode the sequence above. Cap
the walk (64 hops ≈ 1 MB).

A complete dependency-free Python reader is printed in §1 of
<https://pet.doginaldog.shop/technicals.html>.

### Discovery rule

**Content type starts `text/plain` AND the first line is exactly `BEACON/1`.**
That is the entire rule for finding BEACON records among all inscriptions. There
is no allowlist of issuing addresses and nothing to join.

---

## 4. Fees and irreversibility (Dogecoin facts, not BEACON policy)

- **Dogecoin has NO RBF.** A broadcast transaction cannot be fee-bumped. Overpay
  slightly; there is no second chance.
- The fee floor is charged **per started kilobyte**. A 1.87 kB reveal owes the
  2 kB minimum; paying 1.87 kB worth leaves it permanently unmineable.
- Every hop carries a **sub-dust** 0.001 DOGE output, so rate × size alone will
  not get it mined — an absolute per-transaction floor is also required.
- **Never fund from a UTXO carrying an inscription.** Spending someone's doginal
  as ordinary change destroys it. Classify UTXOs and **fail closed**: if you
  cannot classify one, do not spend it.
- **Persist the signed hex before broadcasting.** If you must re-broadcast, send
  the *identical bytes*; a differently-signed retry is a double-spend race you
  cannot call off.
- Unstick with **CPFP**, never replacement. The only "cancel" is waiting for the
  mempool to drop it.

Reference rate used by the operator's own site: `200000000` koinu/kB (2 DOGE/kB),
a few US cents for a whole BEACON record chain.

---

## 5. HTTP API (no authentication anywhere)

Base: `https://pet.doginaldog.shop` (pet vocabulary) or
`https://beacon.hankelsner.tech` (asset vocabulary). One service, two front
doors; the `Host` header selects labels and theme, never content.

| Method | Path | Purpose |
|---|---|---|
| GET | `/api/health` | `{"ok":true,"service":"beacon","spec":"BEACON/1"}` |
| GET | `/api/stats` | index counts: `beacons`, `by_status`, `by_profile`, `missing` |
| GET | `/api/profiles` | **full machine vocabulary** — profiles, labels, statuses, schemes, limits. Fetch this instead of hard-coding |
| GET | `/api/spec.md` | the specification as Markdown |
| GET | `/api/agent.md` | this file |
| GET | `/api/record/<txid>` | the folded record (see shape below). `?refresh=1` forces a chain re-check |
| GET | `/api/lookup?q=&profile=&status=&limit=` | search; `limit` caps at 200 |
| GET | `/api/recent?limit=&profile=` | newest first; `limit` caps at 100 |
| GET | `/api/qr.svg?text=&px=` | QR as SVG (`text` 1–900, `px` 64–1024) |
| GET | `/api/cert/<txid>.svg` | render the certificate |
| GET | `/r/<txid>` | human record page, **server-rendered, zero client JS** |
| POST | `/api/index` | `{"txid":"…"}` — ask the resolver to index a record. **The open door.** 20/min/IP |
| POST | `/api/cert/preview` | render a certificate without inscribing. 60/min/IP |
| POST | `/capi/chain/broadcast` | `{"hex":"…"}` — relay a signed transaction (shared crypto engine, keyless, optional) |

`GET /api/record/<txid>` returns:

```json
{
  "txid": "<64 hex>", "inscription_id": "<64 hex>i0",
  "view": {"name": "...", "id": "...", "status": "MISSING",
           "status_label": "Missing", "profile": "PET",
           "lines": [{"label": "Species & breed", "value": "..."}]},
  "keys": {"T": "PET", "ID": "...", "N": "..."},
  "genesis": {"keys": {}, "unknown": {},
              "meta": {"text": "...", "bytes": 0, "block_height": 0,
                       "block_time": 0, "confirmations": 0,
                       "inscribers": ["D..."]}},
  "holder": {"address": "D...", "traced": true, "custody_chain": ["D..."],
             "hops": 0, "note": null},
  "authority": ["D..."],
  "history": [{"txid": "...", "authorised": true,
               "changes": {"S": "MISSING"}, "block_height": 0, "text": "..."}],
  "applied": 1,
  "rejected": [{"txid": "...", "reason": "not authorised"}],
  "first_seen": 0, "checked": 0
}
```

`keys` is the record **after folding**. `holder.traced: false` means the transfer
trace stopped early (hop cap 32) — the address shown may not be current, and you
MUST say so rather than presenting it as fact.

Error codes: `400` malformed input or invalid record · `429` rate-limited ·
`502` the chain indexer is unreachable — **transient, retry; it is never proof
the record does not exist** · `404` no such route.

---

## 6. Indexing: how a record actually gets listed

**Any valid BEACON record enters the index regardless of who inscribed it or
which tool they used.** There is one code path; the index has no concept of
"ours" versus "theirs". Three routes get a txid in:

1. **Submission** — `POST /api/index {"txid": "…"}`. Open to anyone, no key,
   free, 20/min/IP.
2. **Resolution on first read** — `GET /api/record/<txid>` or `GET /r/<txid>`
   for an unknown txid resolves it from the chain on the spot and stores it.
   Being in the index is *not* a precondition for resolving.
3. **Custody sweep** — on refresh, every address that has held a beacon is swept
   for further BEACON inscriptions naming it in `P`, so an owner updating from
   the wallet holding the doginal is picked up automatically. Bounded: 4
   addresses × 60 transactions, 5-minute cache TTL.

**Stated honestly: there is no full-chain crawler today.** A record whose txid
nobody ever submits or looks up sits on the chain correctly and unlisted. That is
a gap in the *cache*, not the protocol — the record is real and readable the
whole time — and the discovery rule in §3 is published so anyone can build the
crawler that closes it, without anyone's permission.

**Do not tell a user a record "does not exist" because a resolver has not listed
it.** Resolve the txid, or check the chain.

---

## 7. What must never be written into a record

Permanent and public are the same sentence. An update can *supersede*
information; nothing can erase it.

- **No home street address.** Line 5 is an area — a town, a postcode, a site name.
- **No government identity numbers, no payment details, no passwords.**
- Use a contact channel the user is willing to keep or abandon: a phone number,
  an email address, or a URL they control.
- Assume everything ever written stays readable forever.

**If you are drafting a record on someone's behalf, say this before they spend.**
It is the one part of the process that cannot be undone by any later action.

---

## 8. Worked sequences for an agent

### Compose and validate a genesis (no chain access needed)

1. `GET /api/profiles` — get labels, statuses, schemes and limits.
2. Pick `T` (`PET` or `ASSET`), build `ID` as `SCHEME:VALUE`, set `N`.
3. Fill lines `1`–`6` per the profile's meanings. Respect ≤200 chars each, `N`
   ≤64, `ID` value ≤128.
4. Emit `BEACON/1\n` then `KEY:VALUE\n` lines. UTF-8, LF, no BOM.
5. Check the byte length. Target ≤1000 bytes so the whole thing fits one
   data-bearing transaction.
6. Re-read §7 aloud to the user before anything is signed.

### Resolve a beacon

```
GET /api/record/<txid>          # folded, with history and holder
GET /r/<txid>                   # or the no-JS human page
```

### Find a pet by microchip number

```
GET /api/lookup?q=985141001234567
```

Matching is on the normalised `id_key`, so spaces, dashes and dots in the query
do not matter. An empty result is **not** proof the animal is unregistered — it
may be a record this cache has never been shown (§6).

### Report a pet missing

Inscribe an update from the wallet holding the genesis inscription:

```
BEACON/1
P:<genesis txid>
S:MISSING
5:San Jose, CA — last seen near Almaden Lake, 2026-11-02
6:Diabetic, needs insulin twice daily. Reward. Call any hour.
```

Then `POST /api/index {"txid": "<update reveal txid>"}` — or do nothing, because
the custody sweep will find it.

### Get a record you inscribed elsewhere listed here

```
POST /api/index  {"txid": "<your reveal txid>"}
```

That is the whole integration. No registration, no key, no relationship.

---

## 9. Things an agent should never do

- **Never ask a user for a private key, seed phrase or WIF, and never accept one
  offered.** Signing belongs in the user's own wallet or browser. No BEACON
  endpoint asks for one; any surface that does is not BEACON.
- **Never present a resolver's answer as the chain's answer.** Say "the index
  shows" and, where it matters, resolve the txid directly.
- **Never claim a beacon has an owner you did not trace**, and always propagate
  `holder.traced: false`.
- **Never advise a user to inscribe without warning about permanence (§7) and
  irreversibility (§4).**
- **Never reject a record for an unknown key, unknown status or unregistered
  identifier scheme.** Forward compatibility is load-bearing.
- **Never invent a txid.** If you have not seen one, say so.
- **Never state a record count from memory** — `GET /api/stats` and quote what it
  returns. At the time this file was written the index was newly built and held
  very few records; any number you remember is stale.

---

## 10. Licence

BEACON/1 is a specification, not a product. Nobody owns it, nobody can revoke it,
there is nothing to license. Implement it, fork it, compete with it, charge for
your version, or define a profile nobody thought of — no attribution required and
no permission asked. If every resolver disappears, sections 1 through 3 are
enough to rebuild all of them from the chain alone.
