A wooden desk at night with six overflowing paper inboxes, each connected by a copper wire to one neat stack of paper beside a notebook and pen.

Capture Everywhere, Think in One Place

I read a lot. News comes in through RSS feeds, ideas come in through podcasts, and the slower stuff comes in through books and magazines. A surprising amount of it ends up in what I write here and in the work I do at Vycari. An article I skimmed in March becomes the counterexample I need in a design discussion in August. A talk I half-watched on a Saturday becomes the seed of a blog post.

That only works if I can find the thing again. Two years ago I had the same problem with podcasts, and I built a search system over my listening history to solve it. This time the shape was different. My notes live in plain markdown files in Obsidian, and that vault is where I think. The problem was that my reading didn’t live there. It lived everywhere else.

Six Inboxes and No Desk

Readwise held my RSS feeds, my Kindle highlights, and my Twitter bookmarks. Karakeep, the self-hosted bookmark manager on my homelab, held the links I saved from my phone. GitHub stars were an input I cared about as much as any article, and they lived in GitHub. Each of those places was a fine place to capture something and a terrible place to find it three months later.

YouTube was the worst of them, and it’s what started this. I watch a lot of talks and technical videos, and almost none of that made it into my notes. I wanted a thumbs-up on a video to mean something, the way a highlight in a book does. I tried IFTTT and Zapier first, and neither would do the simple thing I wanted, which was to take a liked video and put it somewhere I could triage it. I’d forgotten that detail until I sat down to write this. The whole system grew out of one missing integration.

I’d built the capture habit. Starring a repo, liking a video, or bookmarking a page costs nothing, which is exactly why I did it constantly. The idea is borrowed from David Allen’s Getting Things Done: get everything out of your head and into an inbox, then process the inbox on a schedule. The inbox half stuck with me. The processing half never did. I have never once had the discipline to sit down for the weekly review, and so the highlights and stars piled up in their silos. When I sat down to write I worked from memory and a few frantic searches.

Last November I wrote about personal software, software written for an audience of one. The thing I noticed then was that I’d stopped searching for tools and started building them. This problem was the next test of that idea, and it showed me something about how the idea is changing. That post was about small utilities and one-off scripts. What I built this time is a service, with a scheduler, a status page, secrets management, and a CI pipeline, and I built it the same way I’d build a twenty-line script. The tools I write for myself are getting more sophisticated, and the cost of that sophistication hasn’t gone up. I want to walk through what I built, because the shape of it says something about where this is going.

One Pipe Into Readwise

The decision that made everything else simple was to pick one database and route everything into it. That database is Readwise Reader. A purist would say the daemon should write markdown straight into the vault and skip the middleman, but Readwise already held half my sources and did that job well, with good iOS and browser clients and a maintained Obsidian plugin that pulls everything into the vault on a schedule. Nothing there was broken, so I didn’t fix it. I built only the missing pipe: the three sources that had no way into Readwise at all.

So I opened a coding agent and described a small daemon. It polls a set of sources, pushes anything new into Reader, and dedups against what’s already there. The result is sync-to-readwise, about two thousand lines of Python that has been running in a Docker container on my homelab since May. It handles the three sources Readwise couldn’t reach on its own: YouTube likes, GitHub stars, and Karakeep bookmarks.

The core of it is an interface with one method.

class Source(ABC):
    name: str
    default_location: str = "later"
    default_tags: tuple[str, ...] = ()

    @abstractmethod
    def fetch_candidates(self) -> Iterable[Item]:
        """Yield candidate items. The Syncer handles dedup."""

A source yields items with a URL, a title, and some metadata. The syncer asks Readwise whether it has already seen that URL and saves the ones it hasn’t. That’s the whole design. Adding a new source is one file and a line in a registry. That’s how the project went from YouTube likes on day one to GitHub stars the same afternoon, and Karakeep bookmarks over the summer.

The GitHub source is a good example of how little a source has to do. It pages through /user/starred, and for each repo it hands back the URL, the full name, the owner, and the description. Reader does the rest, fetching the README and filing it as an article tagged github. When I star a repo now, its README shows up in my reading queue within the hour.

The interesting engineering was all in the parts that touch Readwise, not the sources. Reader’s API allows twenty list requests a minute. A cold start that walks your entire library to build the dedup set runs straight into that limit. My first version had the two rate limits transposed, so it paced the generous endpoint carefully and hammered the strict one. Worse, it was walking my RSS feed items, which on a real account outnumber the things I’ve deliberately saved by orders of magnitude. The homelab deployment paged through 103,819 feed documents without reaching the end. The fix was to exclude the feed from the dedup cache entirely, persist the cache on disk, and warm it incrementally on each run. None of that was hard, but it was the kind of thing you only learn by running the tool against your own account.

It’s also a reminder that the agent didn’t notice either problem. I did, because I was reading the logs and knew what my account looked like, and then I had to ask for the cache. There’s a whole post in that, about what the job of an engineer becomes when the agent writes the code, and I’ll get to it soon.

Secrets live in Doppler, the same shape we use for Pepper at Vycari, so the only thing on the host is a service token. There’s a small status page that shows each source’s last sync, its counters, and whether the YouTube OAuth token has expired. If it has, there’s a link to re-authorize from the browser. I added that after the second time I had to SSH in to fix a token, and because I wanted a way to glance at the service and know what it was doing. Since then the token has stayed put and the service has just hummed along.

Claude Files It

Readwise’s plugin lands new documents in a Readwise folder in my vault throughout the day. That solved the finding problem, but it created a filing problem. A folder with a few hundred articles, videos, and repo READMEs is a pile, not a system.

That is where Claude comes in. I have a skill in the Claude desktop app that runs on a schedule and sweeps the new Readwise material into the projects it serves. Each project in my vault has a landing note that summarizes what the project is about. The skill reads those, reads the new documents, and appends each one to the right project’s sources note with a line or two on why it belongs there. When enough unfiled items cluster around a theme that isn’t a project yet, it proposes one. The sources note for this blog as a whole, not for any one post, now has an entry for an IEEE Spectrum piece on skill atrophy under automation. Next to it is a note that it pairs with a draft I’m working on about agents and code review. I didn’t file that. I read the article on my phone, it flowed through Readwise into the vault, and Claude worked out where it belonged. I wrote about scoping an agent to a project’s context earlier this year, and this is the same idea applied to intake rather than writing.

The most common miss is that Claude can’t place something and leaves it in an inbox for me to sort by hand, which is still a fraction of the sorting I’d be doing if I did all of it by hand. The other miss is that it attaches an article to a project where it doesn’t really belong. When that happens I delete the line while I’m reviewing the sources for that project, which I do every time I sit down to write from them. I’d rather have a few sources misfiled than never see them at all, which is where I was before this project. That review is the human check, and it’s the part of GTD I could never sustain on my own. The agent does the weekly review as my research assistant, and I do the reading when it matters.

The effect is that capture is effortless and organization is close to free. I star a project, thumbs-up a video, bookmark a page, or highlight a paragraph, and I don’t think about it again. When I open a project to write, the reading I did for it is already there, with notes on why I saved it.

What I Didn’t Do

I did look for an existing tool, briefly. IFTTT and Zapier were the obvious candidates and neither fit. A year ago that’s where the evening would have gone: reading comparisons of read-it-later services, trying two of them, and settling for the one that covered four of my six sources. Instead I sat down one Saturday, described exactly what I wanted to a coding agent, and let it build. A second Saturday over the summer added Karakeep. A few hours each time, and I learned something on both of them, which felt like a fair trade. Between those two sessions the tool has cost me almost nothing to run. It covers the three sources I was missing and nothing else, and the whole system now covers all six.

The audience of one keeps paying off. The tool has no settings I don’t use, no features waiting for someone else’s roadmap, and no update that quietly changes how it works. When Readwise’s rate limits bit me, I fixed it that afternoon, which is the responsibility I signed up for when I stepped outside managed software. That trade has been worth it every time.

The Part That Worries Me

Lately I’ve noticed the same attitude creeping toward software I already use and pay for. I run a window manager that does ninety percent of what I want and is missing one feature I care about. More than once I’ve thought about having an agent rewrite the whole utility so it does exactly what I want and nothing else. I haven’t done it yet, but the fact that it’s a live option is new, and I don’t think it’s entirely good.

To be clear, my private rewrite wouldn’t hurt anyone. I have no desire to sell or support any of my tools, and most of them aren’t even open source, because I don’t want to field issues or review pull requests for something that was only ever meant for me. This sync tool is public because I wanted to write about it, not because I want anyone else to run it. What worries me is the pattern, not my copy of it. As building for an audience of one gets easier, more people will reach for it first, before they look for the thing that already exists and does most of the job. The people who build those ninety-percent tools are mostly small independent developers, and a world where their potential customers default to rolling their own is a harder world for them to make a living in. I don’t have an answer for that. I notice that I’m part of it.

What I get back is time. I spend almost none of it organizing and much more of it thinking, and the thinking is better because the raw material is at hand. This is the first post in what I expect to be a series on the personal software I actually run. The next one will be about a tool I built for a very different kind of problem.

Leave a Reply