Living with an Append-Only Log

exponentialbuilding-in-publicarchitecture

It’s been about a month since I started Beats, and I’ve been using it daily across multiple projects. Time for an honest assessment.

What works: the append-only log

The core design decision, an append-only event log as the source of truth, has been validated repeatedly. Every feature I’ve built since feels natural because the data model supports it without fighting.

Need to see what changed on an issue? Walk the events. Need to undo? Append a reversal event. Need to understand why something is in its current state? The full history is right there, git-blamed with who and when.

The git-native aspect is paying off exactly as hoped. When I branch for a feature, the issue state branches with the code. When I merge, the issue changes merge too. There’s no sync problem because there’s nothing to sync. The issue tracker is part of the repo.

I keep thinking about the markdown files I used to track work in. Eighty items in a flat document with checkboxes. The moment I needed to know when something was done, or why I decided to do it this way instead of that way, the markdown file had nothing to offer. The event log captures all of that automatically. Every state transition is a timestamped, attributed event. I never have to remember. I can look it up.

What breaks: the flat file

The JSONL file is hitting limits. Not performance limits, even with hundreds of events it’s instantaneous. But structural limits. As issues accumulate comments, relationships, and richer metadata, the projection step (replaying all events to compute current state) is getting more complex. The schema I started with in December was too flat. It didn’t account for the richness that even a simple project management tool needs.

I need a v2 data model. Not a migration in the traditional sense. The beauty of the event log is that I can change how I project events without changing the events themselves. But I need to rethink what an “issue” looks like when it has comments, dependencies, labels, estimates, and relationships to other issues.

What I underestimated: relationships

Parent-child was the obvious relationship, and I built it from day one. But real projects have more: blocking dependencies, related issues, duplicates. The “link” concept needs to be first-class. When I’m looking at an issue, I need to see what depends on it and what it depends on.

The other thing I underestimated is how important the display of relationships is. When you run beats ls, seeing issues grouped by their parent epic makes the backlog legible. Without grouping, fifty issues in a flat list is noise. With it, you see the shape of the work.

Dynamic column widths were a small change that made a big difference: the terminal output now adapts to how wide your terminal is and how long your issue titles are, instead of truncating everything to fixed widths. Small polish, but it’s the kind of thing that makes the difference between a tool you tolerate and one you reach for.

The markdown file problem, revisited

Something keeps nagging me. I replaced my personal markdown todo lists with Beats and it’s better in every way. But I’m still thinking about project planning around the tool, writing design documents, roadmaps, and vision statements in separate markdown files. The tool tracks work, but it doesn’t capture the thinking that produces the work.

An issue says “implement caching layer.” It doesn’t say why we chose this caching strategy over that one. It doesn’t say what we considered and rejected. It doesn’t capture the conversation between me and the agent where we explored three approaches and picked the least obvious one for good reasons.

Those decisions live in my head or in ephemeral chat transcripts that get garbage collected. That feels wrong. The issue tracker should be where thinking happens, not just where results get recorded. I don’t know what that looks like yet, but it’s rattling around.