Writing the same file at once without breaking it

Series · Part 5 — “Building task management for AI agents.” Last time was partitioning the reach by structure. This time is about touching the same file at the same time.

Part 2 showed that the CLI and GUI of Amenbo (https://amenbo.work/) both touch the same store.sqlite. Separate processes write one file at the same time. Normally that looks like it would break. Why it doesn’t is the subject of this part.

Each write is one whole transaction

There are two knacks to it.

The first is to wrap each write whole in a single transaction. SQLite serializes writes, so when two processes arrive at once, one of them waits (up to 5 seconds). They don’t fight and fail — they pass through in order.

The second is to roll back whole when a write fails partway. Why “whole” is needed: earlier, when writes were streamed one row at a time, a crowded moment could leave a half-formed row behind. A write-in-progress row left empty would, by that one row alone, stall reads of the whole store. Since each write became one transaction, a failure leaves nothing behind.

The safety of concurrent writes. Two processes, the CLI and the GUI, write the same store.sqlite (WAL, a single file). Each write is one whole transaction, which SQLite serializes. When they arrive simultaneously, one waits (busy_timeout 5s). A failure partway rolls back whole, so no half-formed row (torn write) remains.
Each write is one whole transaction; if two arrive at once, one waits, and on failure it rolls back.

Changes travel through change_feed

What the CLI writes shows up in the GUI right away. How does that work?

On every write, within the same transaction, it leaves one row describing “what changed” in a small record called change_feed. It holds no values, only which record changed. The GUI watches the file for change, follows the continuation of change_feed, and re-reads only what changed. Only the relevant screen updates; it never re-reads everything.

So even when the CLI or the AI writes from the terminal, the GUI catches up almost instantly, and only where it needs to.

How a change propagates. When the CLI writes, within the same transaction it leaves one row in change_feed describing what changed. The GUI watcher notices it, re-reads only what changed by cursor, and updates just that screen. It does not re-read everything.
A change leaves one row in change_feed; the GUI follows it and updates only the changed screen.

Next time

Part 6: Running AI sessions in parallel. This part was about touching one file at the same time; next is running development with two or three AI sessions side by side — git worktrees, and reservation that only passes from todo, written up as a real case.

☕ Tip me