Getting it into your agent
One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.
npx skills add zakariaf/Flutter-Skills --skill persistence-driftgit clone --depth 1 https://github.com/zakariaf/Flutter-SkillsWrote this? Show the measurements
A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.
[](https://agentmods.dev/skills/zakariaf/flutter-skills/persistence-drift)<a href="https://agentmods.dev/skills/zakariaf/flutter-skills/persistence-drift"><img src="https://agentmods.dev/badge/skills/zakariaf/flutter-skills/persistence-drift.svg" alt="Measured on agentmods" height="20"></a>What it costs to keep this loaded
Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5.1 | $0.00218 | $0.04491 |
| Opus 5 | $0.00109 | $0.02246 |
| Sonnet 5 | $0.00044 | $0.00898 |
| Haiku 4.5 | $0.00022 | $0.00449 |
Grade A, and why
persistence-drift scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 7d ago.
A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.
Nothing flagged
None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.
How it starts
The opening of the file, as written. The whole thing — 226 lines — stays where its author put it; the contents beside it link to each section on GitHub.
Persistence — Drift / SQLite
The on-device store is the single source of truth: with no server, everything must rebuild from this DB alone after
process death, reboot, or restore. Push invariants into the schema, confine Drift to one layer, make every mutation
one durable transaction, and store canonical values only. Applies to any lib/data/ Drift table, DAO, repository,
connection setup, or backup.
Read the reference for the task at hand:
references/schema-and-daos.md— audit-column mixin, STRICT/CHECK/FK invariants, canonical column types, the index +EXPLAIN QUERY PLANgate, DAO↔repository split, files-on-disk-with-relative-paths for blobs.references/backup-and-wal.md— the checkpoint→vacuum→verify-by-reopen primitive, WAL rules, the backup-before-anything lesson, optional SQLCipher (key-first, assert-cipher, header-check).references/persistence-without-drift.md— the same discipline for a small app on plain JSON files (injected base dir, debounce + lifecycle flush, atomic writes, lenient decode).
Run scripts/check-drift-confinement.sh and scripts/check-persistence-bans.sh before a PR. Migrations and their tests: see run-migration.
Non-negotiable rules
- Drift lives only in
lib/data/; everything else sees value types.package:driftandpackage:sqlite3are imported nowhere else — a banned-import lint/grep enforces it. DAOs map rows to immutable value objects; noTable,Companion,TableInfo, or generated row class crosses the boundary. A feature reaching for a raw query is a layering break that also blocks pure testing of everything above it. - Put invariants in the schema, not at call sites. Tables are
STRICT(no silent coercion); enumerable columns getCHECK (col IN (...)), ranges getCHECK (qty BETWEEN 0 AND n)/CHECK (amount_minor >= 0), relations are foreign keys with an explicitonDelete, uniqueness is a (partial)UNIQUE INDEX. A corrupt row must be unrepresentable at the storage layer, not merely policed in Dart. foreign_keysandsynchronousare set inbeforeOpen/setupon EVERY open;journal_mode = WALis set idempotently there too.foreign_keysandsynchronousare per-connection and are not persisted in the file, so they must be re-asserted on every open.journal_mode = WAL, by contrast, is persisted in the database file header and survives across connections — but it is still set idempotently in setup so a freshly created or restored DB adopts it.PRAGMA foreign_keys = ONunconditionally (SQLite defaults it OFF and silently no-ops FK actions when off);journal_mode = WALfor concurrent durable reads;synchronous = FULLon any store holding non-regenerable user data (WAL+NORMAL "might rollback following a power failure"). Seeding, and only seeding, goes insideif (details.wasCreated).- One
db.transactionper mutation; every query inside awaited; persist before publish. A mutation that writes several rows (parent + dependents, or a debit + a credit) is all-or-nothing. A missingawaitinsidetransaction(() async {lets a query run after the transaction closes — Drift calls this data loss; it is a release blocker. The DAOFutureresolves only after the durable commit; the committed write then makes the watched.watch()stream re-emit the new state on its own — never an optimistic pre-commit update, never a manualstate = …republish, never "save later". (The write→UI-update rule is owned bystate-management-riverpod.) - Store canonical values only; convert at the presentation edge. Money as integer minor units keyed to the real ISO-4217 exponent (never a
REAL/double), other quantities as SI integers, true instants as UTC epoch millis. The local calendar day (anything that drives a day boundary) is a serial-day integer, never aDateTimeinstant — an instant reintroduces the DST/timezone off-by-one. No display strings, localized numerals, or formatted values in any column; switching locale/unit must leave stored rows byte-identical. - Derived state is recomputed on read, never stored as a second authority. Counts, streaks, running totals, histograms are pure folds over their source rows, computed by one
watch…/query fold next to the data. A stored copy is a second source of truth that drifts and that any future sync must reconcile twice. - The repository is the single write path and the single source of truth. DAOs hold single-table queries; repositories own cross-table transactions and row→value-object mapping, and return a typed
Result<T, Failure>for fallible work. Feature code depends on the repository abstraction, never on a DAO or Drift row. - Reads are scoped
.watch()streams; pagination is keyset, notOFFSET. Never subscribe to an unscoped app-wide stream (it recomputes on every write); scope by owner/entity + time window. History usesWHERE ts < :cursor ORDER BY ts DESC LIMIT n;OFFSETdegrades badly on large tables. - Blob bytes never live in SQLite. Store files on disk (app-private) with only a metadata row; persist the path relative to a base directory and resolve to absolute at read time — an absolute path dies on iOS reinstall/restore when the container UUID changes, and the row survives while the file renders blank with no error. BLOBs bloat the DB and slow every checkpoint and backup.
- Back up via
wal_checkpoint(TRUNCATE)+VACUUM INTO, then verify by reopen — neverFile.copya live WAL DB. A raw copy of a WAL-mode DB captures a torn state across the-wal/-shmsidecars and is corrupt and unrestorable. A backup that was not re-opened and integrity-checked did not succeed. Detail inreferences/backup-and-wal.md. - Schema evolution is forward-only, append-only, and snapshot-guarded — and it is a separate ritual. Bump
schemaVersion, add a newstepByStepstep (never edit a shipped one), commit the schema snapshot, and ship no migration without a content-level test. That whole workflow lives inrun-migration; this skill defines the tables it migrates.
What ships with it
8 files beside SKILL.md in the same directory: the scripts, references and assets a skill reads on demand. Not counted in the per-session cost; read them before you install if any of them is executable.
- examples/in_memory_dao_test.dart 2.1 KB
- examples/scoped_watch_repository.dart 2.6 KB
- examples/transactional_write.dart 2.6 KB
- references/backup-and-wal.md 5.5 KB
- references/persistence-without-drift.md 3.2 KB
- references/schema-and-daos.md 8.3 KB
- scripts/check-drift-confinement.sh 1.4 KB runs code
- scripts/check-persistence-bans.sh 2.2 KB runs code
What this file has done since we first saw it
Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.
- 7d ago First seen · 226 lines · 218 tokens per session scan A fbc122997aa7
persistence-drift is a skill published in the GitHub repository zakariaf/Flutter-Skills (2 stars, last pushed 9d ago), licensed MIT. It adds 218 tokens to every session and 4,491 once invoked, about $0.0011 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-31.
Other skills, from other repositories
android-persistence
Implement Room schemas and DataStore preferences with proper async patterns in Android. Use when the primary task is storage schema, DAO, migration, or preference isolation; defer auth-token/security storage, any CoroutineWorker/WorkManager task, Hilt graph wiring, and cache-policy design.
firebase-database
Use when syncing real-time data, structuring JSON trees, reading/writing, creating listeners, enabling offline persistence, managing presence, sharding, or writing security rules.
common-store-changelog
Generate user-facing release notes for the App Store and Google Play from git history (App Store <=4000 chars, Google Play <=500). Use when generating release notes, app store changelog, play store release, or "what's new" text for a mobile app.
android-navigation-3
Install and migrate to Jetpack Navigation 3. Use when implementing Navigation 3 patterns including NavDisplay, NavKey routes, deep links, multiple backstacks, scenes (dialogs, bottom sheets), or migrating from Navigation 2.
flutter-auto-route-navigation
Implement typed routing, nested routes, and auth guards using autoroute in Flutter. Use when the task explicitly uses autoroute or its generated router; defer generic deep-link setup and other routing libraries.
flutter-dependency-injection
Configure service locator setup using injectable and getit in Flutter. Use when wiring dependency injection with getit or injectable.