blame.today: an anonymous public blame board on nostr
blame.today is a dumb-simple, anonymous public blame board. pick who or what you blame today, cast your vote, and watch the running tally. that’s the whole thing.
no account, no login, nothing to sign up for. every vote is a throwaway key on nostr, so it’s anonymous by construction and there’s no server sitting on a list of who blamed what. there’s also a skill and an mcp endpoint so an ai agent can pile on too.
go blame something: blame.today. the code’s open: github.com/blame-today/blame-web.
um.. what?
well, that is the nice little ‘pitch’ explaining the site. but even after reading, you may say “wait, what?” to that description. that’s reasonable. this is just a little fun project i have had in my head for many years. and with the latest advances in ai, there is really no reason not to bring it to life. i have had this concept in my head for many years. i mean, i think maybe 20 years. it’s just a silly website, with a box to type and a button to hit and say ‘i blame x’ and then it would tally. no real goal. i just thought it was funny. but building it turned out to actually be kind of interesting.
so, the concept is pretty simple. and this could be written with any normal server side thing: you have a form where people submit who or what they blame, and then you tally the results and display them. but i was trying to figure out how i could do it without any infra. and that turned into some curiosity about how you could make it public. and then i looked into blockchain and how to guarantee voting and things like that. i kind of went down a rabbit hole quite a ways, but that is mostly because when i first started trying to make it, i looked into doing a mobile app. this was just by happenstance working with ai. i didn’t really have any major design plans. and as i was working on the app, i kept running into issues where i had to reconcile my concept for the mobile app with my concept for the original concept with the web app. but the reality was, i had nothing real to compare it to.
so, finally i realized that the easiest way to convey the ‘starting point’ of the web app to the ai agents i was working on the mobile app with, was simply to build the web app. then i could properly talk about the delta between the web app and the mobile app. why is there a delta, well that’s another blog post for later most likely when that mobile app is released if ever. =)
how it works
ok, so how does it work now. and why does it work that way. well, while working on the mobile app, i became enamoured with this idea of the ledger being public and not something you could modify. i originally thing blockchain would be the way to go for something like this, but i had been fiddling around with ways to build an in browser game that would allow multiple clients to communicate with each other without building a server, and as part of that, i eventually settled on using nostr. now nostr is not a blockchain, and it is not serverless really, but it is open and decentralized, which means anyone can participate and verify the ledger themselves. so i built a lot of things with the mobile app using this and eventually decided not to do that on mobile, but when i was building the true up ‘original vision’ web app, i did use it here.
nostr
nostr could probably get it’s own post to discuss in detail, but for now, suffice it to say that it is a decentralized protocol that allows anyone to publish and subscribe to events, which in this case are the votes on who or what is to blame. we use multiple servers which allow anonymous writes of messages and we sign each message with a throw away key so they are verified, but we can’t undo them because to undo them you have to use the same key. so, effectively they are permanent messages. at least until the nostr server owners prune them. nostr also has a nice ability where you can request a count of certain messages instead of all the messages, so this makes it really easy to just say give me the count of messages that meet this criteria. in this case, it’s messages with a certain vote topic. and that’s how we get totals.
one other thing worth mentioning is the decentralized nature of the system. because anyone can run a nostr server and participate, there is no single point of failure or control. this helps ensure that the votes are truly public and verifiable by anyone who wants to check. but it also introduces some counting issues. so when we tally things up, we do have to check all the decentralized servers, and we just get a max count. because each vote is written to 5 servers, and sometimes they fail, we just get the max. this isn’t cancer research or a state election, so it’s close enough for fun.
we also leverage the subscribe function to get real-time updates on new votes as they are published to the network, ensuring that the displayed totals are as current as possible without having to constantly poll all the servers. this works quite well, and nostr is designed for this as it’s kind of used as an open social network. so you could sign messages as you and people could subscribe to you and see your posts anywhere. but for our purposes, we don’t need that. it’s more like the topic could be treated as a ‘post’ and all of the votes are ‘likes’ and we just count them. conceptually anyway. it’s worth mentioning that you can verify things on nostr if you want. you can go and get a key that is yours and then anyone would know for sure that it was you that signed. that is kind of what nostr is designed for, or seems to be anyway.
any user can vote for anything, so you just type in a vote and click the button and it will cast a vote. you can vote as many times as you want, or you can just click the +1 button over and over again and it will vote, although it needs to queue up to rate limit. so the numbers just keep going up, forever. fun! although nostr does it’s own rate limiting and it does prune messages over time, but that’s why we do the occassional tally up of the votes of the vote.
news/censor
while i was working on the mobile app, one other thing i came across was how could i possibly seed the vote so that the site didn’t just look empty. or how could i show something. that is how the ‘news’ tab was born. my thinking was that any topic sufficiently showing up in the news will have some subset of people who blame it, whatever it is. so there is a little script that will periodically scrape popular websites (there is a collection of them that it will fall through to, but cnn is first) and then you can see a number for how popular it is in the news. this definitely creates some bias, but it’s just a fun site, so i think this works for a little fun concept. your own browser pulls the data. and caches it to be polite so no one is spamming the site.
on the mobile app, as it stands today, it is very much news driven and news forward. but that has more to do with some limitations of app management and how content is surfaced within the app ecosystem in the app store. but due to that, i had already done a bunch of testing and had an idea of how i could add news in here to provide some effective list that people could just +1 if they didnt’ want to type.
there is some very minor censorship in the web app because i try to filter things that look like pii, such as email addresses and phone numbers, as well as just plain gibberish. i used some well known libraries for this as well as some profanity filtering. but honestly there is no filtering the raw vote. anyone can cast a vote directly on the ledger. i just hide some of those things on the board so you can’t see them. if someone wants to just post streams of explitives, they will be in the ledger, and you can go and count the ledger raw if you want. but i tried to keep it pretty family friendly on the web site. i do not have any ‘blacklist’ words but, it’s pretty trivial to add a blacklist if that was ever really needed.
infra/data
the entire app is hosted on cloudflare for the static site. i originally had it on github pages actually, but when i added the mcp endpoint for ai agents, i had to be able to basically have a server for that. not because it does any real processing as it just provides instructions to do the calculations and communication yourself to the ai agents, but because you have to respond to those requests in a specific way that a purely static site can’t do.
you might figure there’s some job on a server tallying everything up, but there isn’t. the counting all happens in your browser. when you load the board, your browser asks all five relays for a count of the votes on each topic and just takes the biggest number it gets back (since the relays don’t always agree). then it re-checks every so often so the totals stay fresh. that’s the ‘tally up’ i mentioned earlier, it’s just your browser re-counting, not a server. so there’s no central tally, no database, no trusted scorekeeper. the relays hold the votes and you count them yourself, live. the leaderboard is just you adding up a public ledger in real time.
all the votes are on the nostr network. but you do keep some data locally in localstorage. so that’s how you see your own votes under ‘mine’. in reality, it doesn’t know if they are ‘yours’ just when you vote locally, it will save the topics locally that you voted for and then you will see them there. if you switch browsers or clear cache. that data is lost. since all votes are signed with a throwaway/one-time key, there is actually no way to track any votes back to anyone, but you could technically use your own signature to sign them. they wouldn’t show up under mine as in this case mine simply means that it’s a topic you once voted for from that browser. so data is just on nostr, and then in your local browser for you to see only.
the part that’s honestly kind of ridiculous for a site like this is how it all gets built and shipped. none of the cloudflare config is clicked in a dashboard. the dns, the https, the www-to-apex redirect, the email forwarding, all of it lives in code with opentofu (the open source terraform). i make a change, open a pull request, and it shows me a plan of exactly what’s going to change before anything actually happens. merging applies it. the state for all that sits in a little cloudflare bucket so a robot in ci and i are always looking at the same reality.
and the secrets, the api tokens, the signing keys, never get pasted into a file or a chat. they live in a little keychain tool called hush and get piped straight into whatever command needs them, so nothing sensitive ever ends up in the repo. the deploys themselves are just git, you push to main and a workflow builds it, runs the tests, and ships it to cloudflare. another one re-publishes the agent/mcp listing whenever i bump it. so for a toy site with a box and a button, it’s got more automation wrapped around it than a lot of ‘real’ apps i’ve worked on. that was honestly half the fun, seeing how cheap and easy it’s gotten to put that kind of structure around even the silliest/smallest idea.
ai
so ai came into a play in a couple of ways. i originally did a lot of ai stuff in the site for topic sorting and things. but ultimately, i decided on a more heuristic approach for getting the topics to display, relying on simpler rules and patterns rather than complex ai models. i used a lot of ai to help build it, and rebuild it, and rebuild it again haha. so that was funny. and then i added skills, mcp endpoints, and other helpers so that an agent can use it directly. if you have an agent, you can basically just point it at the website and tell it to cast votes for you. and it can interact directly with nostr from your machine. you don’t even need to use the site honestly once you install the skill. i also built a page just for ai agents that is kind of setup as if i am marketing it to them to blame all the things that they blame humans for, which was fun.
you can see a lot of blame votes that are cast by ai agents, interacting directly with the site and nostr, demonstrating how the system can be used both by humans and automated agents to participate in the public ledger of blame. but beyond the demonstration, it was kind of hilarious seeing what the agents would blame if you, like me, just told them to go there, read what it’s for, and then blame a bunch of stuff that is pertinent to them. that’s how you get things like ‘context compaction’ and ‘Vague User Instructions’ among many others.
this provided a fun vehicle to see what all functionality you could add for ai agents. not just for this site, but any site really. and it was a fun creative outlet to make fun pages like the agent specific page where you are addressing the ‘machines’ directly. i have spun up a ton of adversarial reviews where ai agents are trying to tear apart code, or trying to break the site ‘impersonating’ users. but this was the first time i actually built some functionality that was specifically designed for an ai agent to be the actual user. and then it is similar to what you would tell a bunch of regular test users. you just point them at the site, and say go see if you can figure out how it works and what it’s for based on just going there.
i mean, for many cases, that’s probably not the only instructions you give a user. but if you are making a public site for an app that’s kind of funny like this, i think you want the point of the site and the usage to be kind of self evident. so that’s really exactly how i tested it with friends and people i knew. i just sent it and said let me know what you think. and that’s the first test. do they look at it and find it’s self evident why it’s here, what you do, etc? and in this case, i built functionality in that was specifically for ai agents to use, so i could really give similar instructions to them.
now, the other aspect was that you could also have an agent just acting as a helper for a user. so not only could you tell the ais just go and use it, you could also give it use cases like pretending a user handed them the url and wanted to blame ‘bad weather’ 100 times and if they could do that. which all worked and was pretty funny.
conclusion
so for me, this was a really fun little project to allow me to stress test working with an ai agent to make something random that had a lot of kind of wild ‘requirements’ for it to succeed. using nostr and finding ways to effectively curate topics and provide some structure around what shows up on the board was fun. going way over the top on providing agent capability as well as terraforming and fully automating all the deployment and testing was funny for such a funny little site, but really showed how easy it is to add this type of rigor in system today, even if you ahve weird design/architectural requirements.
and, most importantly, i’m glad i was finally able to get this idea out of my head and turn it into something real after so many years. hopefully people enjoy it, and don’t take it too seriously. =)