I have a theory that AI-assisted writing is bad because people are lazy about their prompts, and that a constraint that the prompt must be longer than the post[1] would make AI writing fine.
To test this, I gave Claude some instructions plus a 952-word rambling prompt and asked it to write a post. Claude knew the audience was LessWrong and I gave it the advice to not try to be maximally persuasive[2], but otherwise let it write naturally. I initially asked it to interview me but it thought the prompt was sufficient and I basically agree.
The result is 935 words (not ideal -- I should have rambled longer), and I think it's actually pretty good. I disagree with a small number of things it wrote, and the post could probably be shorter, but I think with 5 minutes of editing it would actually be a good post. I also forgot that I already wrote a post about this, so here's the human-written control, which I think is actually worse.
I was pleasantly surprised that it mimics my voice reasonably well without even being asked to. It even ends the post awkwardly abruptly like I do.
A production database should describe the current state of the world: the data your systems need to function right now. It's tempting to also make it remember the past — keeping a column that records when something changed, or never overwriting a row so the old version sticks around. This is usually a mistake. If you need history, the right move is to record an actual log of state changes, kept separate from the database that runs production.
Mixing the two gives you the worst of both worlds. Your production schema gets more complicated and harder to migrate. It becomes harder to tell which data actually affects production versus what's only there for posterity. Performance is typically worse, and you're paying to keep historical data on your most expensive, most replicated systems. Meanwhile the log you get out of this is a bad log, because you almost never capture every state change — only the fragments you happened to design for.
Two patterns show up over and over.
The first is using a timestamp instead of a boolean — deleted_at instead of is_deleted, say. This isn't really harmful on its own, which is exactly why it's dangerous[3]: it gives a false sense of having solved the history problem. It only works for booleans, and only for the specific ones you remembered to do it for. Worse, it records only the latest transition. I've frequently wanted to know when a row changed, only to find it had changed again after the moment I cared about, so the timestamp I needed was already gone.
The second is making records write-only — never updating a row, always inserting a new version. This is genuinely useful when production itself needs the old versions. But when it doesn't, you've made everything harder for no production benefit: every query now has to select the latest version of each row, which is especially painful when you want multiple results, and performance degrades as the table fills with dead history[4].
Here's the part I expect some disagreement on. I think even the non-harmful version of these patterns is a bad idea, because it's a partial solution to a problem that has a clean full solution. If you actually want history, a full log is much nicer — and it turns out to be surprisingly easy to get. A full log gives you every version and every change time of every column, not just the last change time of the booleans you remembered. Capturing it also keeps your production database simpler, faster, and cheaper, because the history lives somewhere else: a slower, less expensive, less replicated system that's fine for data you query rarely.
To be clear about the boundary, this isn't an argument against created_at and updated_at. For plenty of systems those columns are genuinely enough, and you shouldn't build anything fancier. The argument is about what to do once you need more than that. At that point the temptation is to accrete more half-measures — another timestamp here, a history flag there — around a handful of columns. That's the moment to go all the way instead.
In Postgres, the temporal_tables extension is a set of functions and triggers that let you keep writing normal updates to your real table while every change is logged to a separate table with a validity time. There are other extensions too, plus a temporal tables feature in recent core Postgres. Because the trigger-based version is nothing but functions and triggers, you can even attach it only to a replica, so the history exists entirely outside your main production database.
At a scale where it's worth the effort — which it isn't for most startups — you can skip triggers and stream the write-ahead log into another system, building the structured log there and reducing load on production. A common shape is Change Data Capture: Postgres → Debezium → Kafka → Clickhouse. The catch is that you have to configure it to keep the raw change events rather than deduplicating down to the final state, which is the default in a lot of setups.
There are edge cases. With the triggers, your log tables need to contain every column that has ever existed, since a missing column is silently dropped from the log, and you need to drop constraints, because historical rows aren't necessarily valid under the current schema. For the CDC path the details are more involved — the main thing I know is that the consumer must never get blocked — and I'll leave the specifics as an exercise for anyone operating at that scale. And as with any replication, you can do real damage if your replication clients fall too far behind.
I ran into this on a system that integrated with client systems, where we regularly needed to answer questions like "when was this user account created?" or "what did the original version of this job application look like?" — and could only sometimes answer them from our database. We seriously considered a large rewrite to make everything write-only, then abandoned it once we realized the queries would be a nightmare, the performance would be abysmal, and the temporal-table triggers solved every one of our problems far more simply.
My direct experience here is with Postgres. I'm fairly confident SQL Server supports the same idea, and I'd guess most databases can do something equivalent, especially since the Postgres version needs nothing more than functions and triggers.
The writing-specific prompt(s) must be longer than the post. Having a long conversation and then prompting "write me a post about the conversion" doesn't count. The prompt also needs to be 100% human-written and not copied from other sources, except for possibly allowing the author's own 100% human-written notes. Having an AI interview you specifically about what you want the post to be about does count.
I'm not trying to blind Claude to the experiment, since if this was allowed by the AI writing policy, we could give agents style advice in the SKILL.md.
Calling this "dangerous" goes too far. Using timestamps for booleans is fine but if you find yourself doing this for every column, you should really just setup a real log table.
I would have made this stronger: it's incredibly difficult to index and query write-only tables and maintain good performance.
I'm not sure what to think about AI-assisted writing in general. As for the "Don't Use Your Production Database as a Log" post it generated, it seems pretty solid. Some constructive critiques do come to my mind (eg. focusing more on concrete examples). And the vibe feels "manicured" in a way that I don't love, sorta like a cookie-cutter suburban neighborhood with perfect lawns. But I have my gripes with human-only writing as well; compared to human-only writing, my gripes with the AI-assisted version here aren't particularly large.