The Moat Is the Event Log, Not the Database
Most of what you call 'owning your data' is just renting it from yourself. Spent a day inside the data layer of the property management OS and realized the rent roll and the GL aren't the moat. They're a cache. The event log is the only thing that disappears if you don't capture it. Part 2 of a build log series.
This is part 2 of a series on building an AI operating system for running a small property management portfolio. Read part 1 here.
I posted last week about the four layers I’m building this property management OS around: data sync, dashboard, action automation, AI oversight. Sync is running, dashboard is up. I spent today inside the data layer, and while I was talking through what we had actually done, I realized I had the pieces right but was framing one of them a little wrong. Small reframe, but useful enough that I want to write it down while it is fresh.
The thing I was framing wrong: what “own your data” actually means.
What I had been quietly about to do
When you say “own your data” out loud, it sounds obvious that you should copy everything out of your management software into your own database. That feels like the work. I was happily on that path. But most of that data is not actually yours to lose. The rent roll, the tenant list, the bank activity, the GL. The software already structures it and will hand it back any time you ask. Re-hosting it gives you a queryable copy and that is genuinely useful, but it is a cache, not an asset. Lose it, re-sync it in an hour.
Three things that actually disappear if you don’t capture them
The data that vanishes for good is narrower than I thought. Three buckets.
History. The point-in-time version of what you knew when. What did the rent roll look like in March before the renewal push? The software shows you today and yesterday. It does not store the version of reality you were operating on three months ago. If you do not snapshot it, it is gone.
Joins. The money, the messages, and the calls on one timeline, keyed by property and unit and tenant. Each tool shows its own slice. None of them naturally lay all three on the same row, in the same order, with the same keys. That stitching is the asset.
Proprietary exhaust. Decisions and outcomes. What the team decided and why. What we offered and how it turned out. The call where the resident said something that changed our read on them. None of that exists anywhere unless you put it somewhere.
The reframe is just this: the goal is not to mirror every endpoint. The goal is to own the history, own the joins, and capture the exhaust. Let the source systems serve current state on demand. We were already building toward that. I just had not said it cleanly to myself.
The crown jewel: the event log
Once it is said that way, one thing in the system jumps out.
There is exactly one table that holds all three at once: the event log. Every row has when it happened, what happened, who or what it touched, and an actor. With the schema we already have (occurred_at separate from recorded_at, an actor field, an entity type and id) it is the spine the whole system hangs off of.
Lose the rent roll, re-sync it in an hour. Lose the event log, it is gone forever. That is the difference between a cache and an asset.
The honest gap
The event log is flowing. In the last 24 hours: about 15,000 SMS, 2,000 emails, 70 letters, 14 phone calls. That sounds great. But every single one of those rows is a system event. SMS sent. Email sent. Call completed. There is not yet a single decision in there (“not renewing this resident, the late pays keep stacking”), and not a single outcome (“renewed at plus seventy five a month”). Pure activity, no judgment, no results. And judgment plus results is the part the AI agent actually needs to learn from.
How I’m going to close it, cheapest first
One. Derive outcomes from the snapshots I already pull. A small job that compares last night’s reality to tonight’s reality and emits an event when something changed. Lease end date pushed forward and rent went up: that is a renewal completion. Tenant moved from Current to Past: that is a confirmed moveout. Balance cleared after a collections letter: that is a paid event. No one types anything. It just appears.
Two. Harvest decisions from the systems of record where the team already writes them. Task status changes and comments inside our project tool get mirrored into the event log as decision events. Mirror, do not double enter.
Three. One tap capture for the residual judgment. A single button or tag for the phone calls and hallway conversations that live nowhere else. If logging takes more than two seconds, a busy operator will skip it and the log fills with holes you cannot trust.
And the one schema change that makes the whole thing pay off: a cause_event_id on every event. So when an outcome shows up, it points back to the action that drove it. “Letter sent” and “paid in full” stop being two unrelated rows and become a story. A story the agent can learn from, then anticipate, then run on its own.

Where this goes next
That is the work for the next two weeks. The derive job first, because it is free, it cannot lie (it reads reality, not memory), and the moment it ships, the action to outcome links start populating with no human effort.
If you run a portfolio and you are anywhere on this same path, I would actually love to hear how you are thinking about your own event log, or whether you have given up on the idea. Hit reply.