I have been reading stuff on various websites for decades. I’ve been noting down in Markdown docs and sharing what I find interesting with others for around three or four years. As of yesterday, I have been indexing and searching through what I’ve found interesting enough to share.
This is all possible thanks to Hister, an ingenious project I learned about yesterday on Lobsters first, then Hacker News. It is a small, self-hosted “search engine” whose index is populated by a web extension you run on your browser and which piggybacks off of the hard work your browser does to render the page (and get around antibot firewalls) to ingest the content of the page as well automatically. This way, your browser history will be saved and be prepped for search, and in case you need to find:
git so that it pushes to two repos simultaneously with one
remote(All three are examples taken from my life. Thankfully I’ve referenced #2 enough that I never need that StackOverflow answer ever again)
It makes total sense why this should be a locally solvable problem: you’ve already done the hard work of finding a good resource, why redo it all over again with what could be a worse remote search engine (especially after November 2022)?
Now, while I like the web interface for Hister, I realized it could be more interesting to create an alternate interface and source of links for Hister.
I started out saving links I found interesting to Markdown notes, but for a few years now I’ve been mostly sending them to other people, who in turn also send links they find interesting as well. This usually prompts good discussion in which we all come and learn something new.
The shared pool of links used to be on a Discord server, but for a little over a year, we’ve been in a Matrix room and have steadily seen it improve from what it used to be.
Side Note: please use Element X and Fractal, the official Element client is so bad by comparison
Indexing these links was possible anyways, since I’d click on most of them, but there was no guarantee. Plus, if I wanted to index stuff from browsers on multiple devices, the extension would have to connect to the Hister service over Tailscale, and I usually don’t run Tailscale full time on my devices for flexibility and (in Android’s case) battery life.
As I was of late itching to create something for fun, I
decided I would create a Matrix bot that would take links from a group
chat I’m in, and automatically insert the contents into Hister, and also
allow a /search in the same chat to seamlessly get search
results and click on them right there, because why not? We moved from
Discord, slash commands are fun to play with.
However, this is a pretty basic problem. People have been creating Discord bots more complicated than some apps people use. To forcefully inject some novelty, and to keep up with the changing times, I vibecoded the entire thing. See the repo on sourcehut or github
That’s right.
Not. One. Line. Was written by a human. (as of the writing of this article)
(note: this article is 100% written by a human)
I have almost zero idea how the Matrix integration works, nor what the code that queries the Hister backend for search results look like. And yet, in one night (more like 2 hours really), I got the entire thing working.
Q. Are you crazy?
A. Two years ago, I would’ve called me crazy for suggesting this. Now, I call it a Tuesday.
Q. Is this how I would want all software to be built?
A. NO.
I can’t imagine building critical systems completely without any guardrails, checks, code reviews, actual engineers who understand the code and the faults it may have and the bugs that are actually features. LLMs are coming a long way, much better than when I had to make commits almost every single time after theirs to fix issues, but they’re not too far off from that experiment.
I much prefer scoping the work an agent does, just to make sure I can audit it and identify issues (if any).
Q. Did I just say “build me a matrix bot that …..” and I was presented with a fully working, nice looking Go project that did exactly what I wanted?
A. NO.
Thanks to advancements in LLM tooling, namely coding agents such as Claude Code, OpenAI’s Codex, or Google’s Antigravity, LLMs are able to pick the latest libraries, lay down best practices like formatting code or having and writing unit tests and fixing regressions if found, and generally do more and more “good software engineer” actions.
However, I would not advise anyone to do this, because the LLMs don’t know what you want to make as good as you do, and, much like traditional software engineering, the sooner you know what you want to build, the better the project develops. In LLM-speak, your first prompts matter far more than your last few prompts. The effects of the first prompts you give the LLM, the ones to set up the project and start writing preliminary code, those will echo throughout the entire codebase forever.
So, it is important to write well. And in order to write well, you have to know both what to write and how to write it. The first is gained through learning, failing and learning again. The second is done by writing, failing and writing again.
Now, on to what I actually did.
I used amp initially (at the time of writing, it
defaults to Claude Opus 4.6 for smart mode and GPT 5.3 Codex for the
deep mode), and then exclusively codex with GPT 5.3 Codex.
I have a couple agent skills I use that I try to keep language-agnostic
to define common behaviors rather than “write Go exactly this way”.
I had a basic idea of what I wanted: a Matrix bot, written in Go using the Mautrix framework (because I was scouting how to build a Matrix bot anyways), that would call 2 APIs:
/add to add a URL it read from chat, along
with its page title and some basic cleaned up content/search to search for some links and return
the top 5 results in a reply to a
/search <searchterm> command.I first started out by cloning the hister repo, and also using my new
“docker dev environment” skill (from my skills repo) to get
amp to create an appropriate Docker container and tooling
to run hister inside Docker, isolated from the rest of my system.
Ideally, this should run inside an isolated systemd service, but for
development purposes a local Docker container is much easier to work
with.
This wasn’t strictly necessary, except for mapping out what directories Hister would require in the future. Thanks to dockerizing, I knew what directory Hister would use every single time for storing its database, and I had one more data point in favor of this skill.
It actually didn’t create the binding to save the index to disk, though. I had to find that. It also didn’t find it necessary to forward ports to the host, even though this was a web service. Oh well.
Next, I ran a prompt:
“find the API endpoints which are responsible for querying the search index and also for the adding of elements in the db”
Basic, yes, but the idea was to “warm up” the tokens for what came after:
“write all of this inside of a markdown file called HISTER_API.md that can be used by another service to ingest links into the index”
Now I had readymade documentation for codex to use in an entirely different context.
The next prompt was probably the most important, most detailed and
also took place inside of codex’s plan mode, a special mode
where it pretty much has read access to your repo and no tools to edit
etc:
“I would like to use https://github.com/mautrix/go as library to
create a matrix bot that will sit in one group, and listen for messages
that contain an HTTP/HTTPS URL. If there is one, it will perform certain
action (let’s say action is uploading it into a search engine index via
API http://localhost/api/upload). Then, if a /search
This did not fire off straight away into writing a plan of action. It asked me around 11 clarifying questions which helped shape the plan into what it was, questions that would’ve arisen (for me working solo) a bit later into the development process.
I had it create two files, mainly because I was pretty sure the setup docs would not be 100% correct (you can’t really rely on models for factual info, and info on Matrix is relatively harder to come by due to less use and more rapid development making some search results outdated), and I wanted the other doc to be purely about the codebase.
This turned out to be the correct decision, because I then created a
new repo, gave it the spec it generated and set the model to
gpt-5.3-codex high.
It ran for an insane 20 minutes and generated pretty much good code on the “first try”. The way it did this was interesting, it spawned subagents for creating submodules, complete with their own tests, with some agreement on the interfaces binding them together. However, when the main thread tried to build the project and failed, it found that the subagents had more or less violated this relatively loose agreement and it got a new subagent to address the interfacing issues between the submodules.
Once that was done, I seemingly had an entire codebase from scratch!
Now, before we proceed further, I’d like to interject and inform you of something.
In case you don’t find any other resource (because I barely did) for this, to set up a Matrix bot with E2EE properly:
No seriously, unlike Discord, “self-bots” are pretty okay here. I’m sure there are some abuse preventions on official matrix.org homeservers, but for small-scale usage, I think this is the way to go.
I opted to use Element Web to do the sign in since I did not want to install any Electron apps that night, no thank you very much, and Electron’s the easy way to create a Matrix account on matrix.org.
In fact, rather than using access token to register yourself (as some guides online suggest), you should use password based authentication for the first time setup. Why?
Well, whenever you sign into Matrix from a client (obviously using password), keys are generated and the public one is kept on your home server, along with a client ID. The homeserver in turn generates and returns an access token which acts as the “password” for the client. Thus your password is never stored by the client and instead there’s a string of data that other devices can tell the homeserver to invalidate in case that device was compromised or lost.
Now, if you use an access token from, say Element into another client, the homeserver will attempt to use the public key it has registered already. However, the client will try to register its own key with the home server, which causes a clash. Thus registration doesn’t occur.
I found that the easiest way to set up a client was to use password based auth first, and then use the access token and client ID generated for subsequent logins.
Now onto how I learned this:
Matrix is a perfectly fine-ish platform. It tries to do a lot. It simultaneously:
When you have so many use cases to target, a couple flows will inevitably suffer. I myself have experienced a Matrix device setup that has gone smoothly only once, after multiple bad experiences. Historically, the sign in will work fine, for sure, and I can even opt into E2EE. However, importing my key or marking my device as trusted has been the bane of me. I still don’t remember how I did it for my personal account, just that I did it. I think I reset my keys and called it a day on the few E2EE conversations I lost.
So it was, when I was booting up the bot, with an incomplete knowledge on how the Matrix authentication flow works, that I had to experience an hour of debugging why the bot didn’t work.
Like I mentioned above, I was operating on my research that indicated
I would need to use Element’s access token for my bot to impersonate it,
and I could just simply use the recovery passphrase feature to derive
the keys and import them into the bot’s crypto storage (an sqlite
database). However, this was wrong, and after numerous registrations,
when I got ahold of the Matrix whoami API endpoint, I
figured out that every time I called it using password auth, it would
“register” a new device.
So, I gave codex two prompts: one to allow me to authenticate via password, and another to print the access token and device ID to logs when this password flow as being used. This way, a user can run the service and generate the keys, and then re-run it with access token and delete the password from their server.
It is amazing that this was around same amount of time that it took to write the entire codebase (if we include stuff like drafting prompts, setting up hister, fixing hister’s container mounts etc.)
Some features outlined by spec are/were not present after the big plan-based prompt:
Triggering search when a mention of the bot is there is not working.
Downloading webpage and extracting content from there required a separate prompt. This was a big one, so I had codex determine the easiest way to just get a URL and strip it of most HTML and send the text contents to Hister to index. In 2026, scraping pages this way will get you hardly any info, but this just needed to be a quick and dirty hack. Besides, the browser extension was still there, and it would be able to pick up the slack anyways.
There’s also a few minor gripes:
Duplicate search results when links are ingested multiple times (inadvertedly)
Bot addresses requests sequentially, rather than allowing
/search reads to occur even while a big ingestion takes
place.
The bot is pretty great, mostly thanks to Hister, which uses Bleve search to achieve its blazing fast search results. All it is really doing is serving as a nice interface in chat. I managed to extract links from the Discord server and the prior activity in the Matrix room and ingested it all into Hister slowly (so as to not get blocked nearly as often) by DMing the bot on Element with 50 lines of links per message.
Now, I have a decent search engine made up of knowledge built up over the years, and it is continously being added to even when I am not present in the chat, and same for others as well.
To me, this is the perfect use case of an LLM assistant for coding: it is a small, personal project that solves a small problem, and was solved with little effort. It also serves as a decent exercise of LLM agent usage, and also gives me a small Go project to maintain by myself (ie, no LLM). I wonder if switching into the maintainer of this project will change my opinion of this article’s sentiments. For now, though, I am impressed. Next time, we’ll see how easy it is to maintain this codebase and extend it with more cool features.
This website was made using Markdown, Pandoc, and a custom program to automatically add headers and footers (including this one) to any document that’s published here.
Copyright © 2026 Saksham Mittal. All rights reserved. Unless otherwise stated, all content on this website is licensed under the CC BY-SA 4.0 International License