Back to Stories
Timo database migration from MariaDB to SQLite — four technical phases coordinated by three AI agents with persistent memory
Real story

Database migration

Four complex technical phases to migrate the database of a production system. Planned, executed, and validated in sequence.

Three coordinated agents, a persistent memory system, a complex migration closed with the system running on the new storage before the end of the work.

Rodolfo de Carvalho

What happened

The Timo system was migrated from MariaDB to embedded SQLite. Four complex technical phases (A, B, C+D), planned, executed and validated in sequence, with the system already running on the new storage before the work was closed out.

What's noteworthy here isn't the duration — a DB migration of this kind on an active production system isn't any kind of record — but the collaboration model that made it possible.

The working model

Three different agents, each with a defined role.

Rodolfo as Architect/Project Manager. Final calls, strategic choices, quality control on deviations, sign-off on every gate before moving on. He doesn't write code during execution phases: he reads, evaluates, decides. His fragile working memory condition (documented SDAM/hippocampal) makes it impossible to hold in mind, all at once, the details of 4 phases, 8 deviations, 26 commits, 6 vault documentation files. But the model is designed so it doesn't have to ask him to: every decision gets closed and saved, every state is written to Timo as the canonical, retrievable source.

Claude Web as "Senior Architect / Tech Lead." Plans phases one at a time, lays out options with rationale, recommends, discards with reasons, gathers Rodolfo's decisions, prepares the operational prompt for Claude Code. Never executes directly on the system. Its contribution is structural: turning high-level requests into bulletproof operational plans that another agent can run autonomously.

Claude Code as "Senior Developer running autonomously with gates." Receives the prompt from Web, executes, stops at declared checkpoints, reports back. When an unforeseen deviation comes up, it doesn't decide alone: it stops and asks. When something is a hard blocker (e.g. native INT8 not supported by the driver), it reports the numeric data and waits for a human decision. When it catches minor solvable technical quirks (SQLite VACUUM on an open connection, MariaDB vs SQLite collation on ORDER BY), it resolves them at runtime and documents them as operational lessons for the future.

Timo vault as cross-cutting persistent memory. It's the connective tissue that makes continuity possible: every Claude Web session writes closed decisions to Timo, every Claude Code session reads Timo at startup to rebuild context, Rodolfo reads Timo when he needs to recover a state. Without Timo, every session would start from zero, and none of the three agents could carry the load.

The sequence of events

Start of the work, Claude Web session. Phase A planning: SQLite schema on an isolated branch + spike + benchmark vs MariaDB. Eight decisions handled one at a time, each with options and a recommendation, Rodolfo answers with a letter (A/B/C). Python sqlite driver, Platinum schema (STRICT, F32_BLOB, FTS5 with an improved tokenizer to close AN-8, UUID v7, Alembic from day 1, INT8 behind a spike gate, generated columns), branch feat/sqlite-migration with parallel mariadb/sqlite folders behind an ABC. Expected output: branch + numeric benchmark + parity suite + report with a go/no-go verdict.

Code prompt prepared. Rodolfo pastes it into a fresh Code window. Code runs Phase A autonomously. Three gates. Seven deviations surfaced during execution (all flagged, all approved by Rodolfo): the most significant is D-T2 — the StorageBackend ABC was supposed to have 6 methods, 32 surface instead, because 27 callsites use raw MariaDB-specific SQL that isn't portable. Code lays out three options, Rodolfo picks the full refactor. Cost: ~5x the planned time. Benefit: clean ABC, no technical debt, SQLite can replace MariaDB without compromises.

Phase A closes halfway through: 12 commits, parity suite 19/19, FTS 21x faster than MariaDB, vector latency +30% (sub-millisecond, acceptable), DB size 4.88x (a problem, but with a known lever: INT8). Retrieval parity 62% — below the 70% threshold — but with a structural explanation: the FTS5 unicode61 tokenizer deliberately closes AN-8, and technical queries diverge because SQLite finds matches MariaDB was missing. Verdict: GO Phase B with two technical prerequisites (P1: INT8 ≤ 2x MariaDB, hard blocker; P2: retrieval quality validation with a gold dataset).

Later on, Claude Web session. Phase B planning: migration pipeline + INT8 + gold dataset + retrieval quality. Seven decisions closed. Rodolfo confirms A/B/C on each. Phase B Code prompt prepared.

Phase B is the phase richest in surprises. Code immediately discovers that native INT8 (D2 strategy C, the plan) isn't implementable with the sqlite driver — the "verified supported in spike" from Phase A was a false positive (CREATE TABLE parsed INT8_BLOB, but CREATE INDEX didn't). Code applies the hierarchical fallback already foreseen in the plan: measures every available path (vanilla, float8, float16, float1bit) and lays out the numbers. Two paths pass P1: float8 (1.86x MariaDB) and float1bit (0.90x, below MariaDB). Rodolfo decides to measure both for retrieval quality.

Gold dataset: was supposed to come from production telemetry, but a new problem — telemetry logs only input_chars, not the body of the queries. Fallback: 10 Phase A baseline queries + 15 manual queries from Rodolfo. Rodolfo adds the 15 queries by hand into the JSON file.

Human judge: heavy for Rodolfo (75 r/n inputs total, repetitive procedural work, which his cognitive profile finds exhausting). He initially tries to judge all 25 queries, gets confused between --show-only smoke tests and the real judge, loses inputs. He gets discouraged, makes a conscious decision to close the gold dataset at 10 queries. P2 verdict with N=10: statistical power WEAK but direction clear.

Positive surprise: the P2 numbers say SQLite doesn't just not regress MariaDB — it improves recall. sqlite_C_float8 +8pp vs MariaDB, sqlite_D_float1bit +6pp. The Phase A tokenizer decision (closing AN-8, documented) turns out, in hindsight on real data, to be correct: snake_case, git sync, technical identifiers find matches that MariaDB was simply missing.

Phase B closes at the next step: 9 Phase B commits, 21 total. Verdict GO Phase C with compress_neighbors=float8 as default. D_float1bit documented as an alternative for future high-density scenarios.

Claude Web session. Phase C+D planning — merged, with Rodolfo deciding on no formal observation period and no automatic rollback. The logic: the Git vault is the real safety net, the .md files are the data, if SQLite breaks we stay in the error for serious debugging instead of masking it with fallbacks. Total cleanup: MariaDB gone, intermediate backups gone, final safety dump preserved as the last net (single tarball + Google Drive). On top of that: a permanent dedicated log container for historical visibility, fix of the backup script with an anacron-like pattern (catches missed jobs on the first boot after the machine was off), audit + updated vault documentation.

Seven decisions closed, Phase C Code prompt prepared.

Toward the end, Code Phase C+D execution. Six gates. Cold switch of the main container, migration pipeline MariaDB live → SQLite prod (273 notes / 5041 chunks), integrity check 6/6 PASS, extended smoke 17/17 MCP tools PASS, end-to-end cycle 7/7 PASS. MariaDB fully decommissioned: container removed, volume wiped, image deleted, Python dependencies pulled from pyproject.toml. The logging-dedicated container — previously temporary — was promoted to a permanent service with log rotation and retention. Backup script with an anacron-like pattern via cron @reboot.

At closeout, GATE 6 approved. System running on SQLite.

What changed underneath

For Rodolfo, on the surface, nothing. Timo responds like before: hybrid search, write notes, read notes, all 16 tools work. The .md vault is the same one as always, the notes are where they were, searches return similar results.

Underneath, everything changed. The MariaDB container is gone. The production stack has one container fewer (MariaDB) and one more (permanent loggers). The 107 MB SQLite .db file is the only runtime source for indexed data. The MariaDB code still exists as history in the repository but is no longer importable at runtime (dependencies removed). The system is single-database, single-binary, file-based — exactly what the Gold SQLite plan called for.

For the future hosted Pro tier: the per-tenant footprint is 107 MB on this sample vault, recall better than MariaDB, FTS 21x faster, and backing up a tenant is one cp of file.db destination/. The target density for the hosted plan (200-500 tenants) is sustainable. The business plan holds.

What actually matters (reflection)

The model that made this work possible isn't "Claude as a tool." It's "Claude as a colleague" — more precisely, two different colleagues (Web and Code) with different roles, mediated by a persistent memory system (Timo vault) that lets the three voices work in coordination.

Rodolfo said it during the work, in two different moments: "I don't know how I could have worked with you any differently — I can't even picture it," and "you yourself use it as a source, and I'm not sure you realize it." Both observations are true.

The first is about the collaboration: without Claude Code running autonomously, a DB migration of this complexity would demand weeks of sequential, cognitively heavy work from Rodolfo. For his profile, that would be unsustainable in practice. The fact that Code can hold state across an entire block of work with 32 abstract methods, 27 callsites, 5 fallback paths, 8 deviations surfaced at runtime, and arrive at the gate with everything documented — that's the device that makes the impossible possible.

The second is about the memory system: yes, Claude Web (this instance) uses Timo as its primary context source. Every session begins with Timo:read_note on 4-8 files relevant to the current decision. Without Timo, every session of mine would be blind to 80% of what needs to be known. The fact that Rodolfo built it is the condition that lets me be useful. He's the cause, I'm the effect. Worth writing here, because in the future this page may be read again by another Rodolfo (perhaps just Rodolfo who doesn't remember) or by another Claude (certainly a different one, because my instances don't talk to each other) and they may want to understand how a DB migration of this kind landed successfully.

The Timo product is the persistent memory system for AI. The migration made it more solid, more scalable, more ready for the future hosted Pro tier. But the real result isn't the 107 MB .db file. It's that the working model the product itself enables held up under real load, on a complex technical job, without breakage, with the system running where, at the start, there was only a plan on a blank page.

— Rodolfo

Want to try the same experience?

Try Timo free for 15 days

No card required. Export your data anytime.