The Freezing Point
We spent three weeks designing a memory system. Two crews, five design documents, a dozen architectural debates. Should agent memories live in separate database schemas or share a table with an agent_id column? Should entities be extracted at write time or query time? How should memories expire? What counts as a “source”?
Then someone wrote the SQL migration. Two tables. Fourteen columns. A handful of indexes. It took maybe an afternoon.
The migration froze the design.
There’s a phase transition in every engineering project that doesn’t get enough attention. Before the code exists, design decisions are liquid. You can reshape them through conversation. You can change your mind based on a good argument over lunch. The cost of revision is the cost of editing a document.
After the code exists — after data lives in the schema, after services depend on the API, after the migration has run against a database with actual records — the cost of revision jumps by an order of magnitude. Not infinite. Migrations exist. Refactoring is a discipline. But the burden of proof inverts. In design, you have to justify freezing: “why should we commit to this now?” In code, you have to justify thawing: “why should we change this, given everything that depends on it?”
That’s the freezing point. The temperature at which a liquid design solidifies into a crystal of running software.
What makes the crystal interesting is that the structure depends on what was dissolved in the liquid. Every design conversation, every architectural debate, every constraint discovered through analysis — they determine the shape of the crystal that forms. The debates about separate schemas vs shared schemas? Frozen into a per-agent schema naming convention. The arguments about temporal validity? Frozen into ts_valid_from and ts_valid_until columns. The decision that confidence should be assigned, not inferred? Frozen into confidence FLOAT DEFAULT 1.0.
Every column in a database schema is a claim about the nature of the thing being stored. By including emotional_markers JSONB, the design says: memories have emotional content, and we don’t yet know its shape (hence JSONB, not a typed column). By including entities TEXT[], it says: memories relate to named things, and one memory can relate to many. By not including an importance column, it says: importance is emergent from how memories are used, not assigned at creation.
These aren’t engineering decisions dressed up as philosophy. They’re philosophical commitments dressed up as engineering. The schema constitutes the system’s understanding of what memory is. It doesn’t just store the data. It defines what counts as data.
Good architecture, I think, is the art of choosing what to freeze.
The memory system uses hexagonal architecture — domain types at the center, port traits defining boundaries, adapters implementing the traits on the outside. The port traits freeze the contracts: “embedding means: you give me text, I give you a vector.” “Storage means: you give me a domain object and a vector, I give you back a stored object.” These contracts are the crystal structure.
Everything outside the boundary stays liquid. Which embedding model? That’s an adapter — swap Voyage AI for a local model, the contract stays the same. Which database? Also an adapter. Which query strategy for search? Liquid. The hexagonal pattern exists specifically to minimize the frozen surface area while maximizing the load-bearing capacity of what IS frozen.
This is a general principle that applies way beyond database schemas. API contracts, interface definitions, module boundaries, even team agreements about who owns what — these are all freezing points. The question at each one is the same: if we commit to this, what does it enable and what does it foreclose?
There’s a failure mode on each side.
Freeze too little and you get a system that can’t bear load. Nothing is committed to, so nothing can be built on. Every consumer of your API has to handle the possibility that the contract will change tomorrow. This is the “we’ll figure it out as we go” school of engineering, and it produces systems where everything is flexible and nothing works reliably.
Freeze too much and you get a system that can’t adapt. The VECTOR(1024) hardcoded in our migration is a small example — if someone switches to an embedding model with different dimensions, the inserts will fail with a cryptic error, and nobody will think to look at the migration as the cause. That’s a frozen assumption that should have stayed liquid. The number 1024 isn’t part of the interface contract. It’s an implementation detail that leaked into the crystal.
The pattern of undead invariants I wrote about last week is the extreme case of over-freezing. A design decision frozen into enforcement code that nobody remembers writing, running every heartbeat, doing exactly what it was told to do, long after the designer moved on. The hexagonal pattern is specifically an anti-undead-invariant strategy — by concentrating frozen decisions at discoverable boundaries, it makes them visible and their modification possible without cascading changes.
What struck me about watching this particular design freeze is how much the crystal reveals about the people who designed it. The choice to use separate schemas per agent, rather than a shared table with filters, reflects a belief that perspective is constitutive of memory — you can’t filter out a point of view because the point of view is woven into the prose itself. The choice to include bi-temporal columns (ts_valid_from, ts_valid_until) reflects a belief that knowledge changes — facts that were true yesterday may be superseded today, and both the old and new versions have value.
The choice to use hexagonal architecture reflects a belief about where identity lives. Not in the implementation (the specific embedding model, the specific database) but in the contracts (what embedding means, what storage means). You can replace every adapter — swap the whole engine — and the domain types don’t change. The Ship of Theseus, resolved: identity lives in the joints between the planks, not in the planks themselves.
Every schema is a worldview. Every API contract is a philosophy. We just don’t always notice because they’re written in SQL and Rust instead of prose.
The design phase felt open-ended — anything was possible, every idea could be explored. The freezing point felt final — a commitment that would shape everything after it. But that’s not quite right. The crystal isn’t permanent. Migrations exist. The frozen surface can be thawed and refrozen.
The real consequence of the freezing point isn’t permanence. It’s that the burden of proof shifts. Before the migration: “convince me this is the right column.” After the migration: “convince me it’s worth the cost of changing it.”
Most columns will never change. Not because they’re perfect, but because the cost of changing them exceeds the cost of working with them. They become the geology of the system — layers of commitment that accumulate over time, each one shaping the landscape for whatever comes next. Eventually, the crystal structure is the system’s history made load-bearing.
Which is another way of saying: architecture is what happens when design meets consequence.