AGENTS.md
September 12, 2026 · View on GitHub
Guidance for AI coding agents working in this repository.
Project nature
DoltLite is a fork of SQLite that replaces the B-tree storage engine with a
content-addressed prolly tree,
giving a SQL database Git-like version control (branch / commit / merge / diff)
while staying embeddable as a single library. SQLite's btree.h interface is
the architectural seam: the tokenizer, parser, planner, and VDBE remain
upstream-derived, while a DoltLite-format primary database replaces SQLite's
B-tree, pager, and on-disk format with a prolly-tree engine backed by a
single-file, content-addressed chunk store.
The btree.h seam is not a do-not-edit boundary. Changes to upstream-derived
code above it are allowed when DoltLite integration or correctness requires
them, but they must be narrowly scoped and guarded by DOLTLITE_PROLLY so a
DOLTLITE_PROLLY=0 build retains stock SQLite behavior. Prefer solving a
storage-engine concern below the seam when the interface permits it.
DoltLite is developed on Git and hosted on GitHub (https://github.com/dolthub/doltlite). Unlike upstream SQLite, it uses pull requests and accepts agentic contributions — opening PRs is the normal workflow (see PR / git workflow below).
Licensing and file headers
DoltLite is licensed Apache-2.0 (LICENSE.md). This differs from upstream
SQLite, which is public domain.
- Upstream SQLite files (
src/btree.c,src/vdbe.c,src/where*.c, …) keep their public-domain "blessing" comment. Preserve it unchanged; never add a license header to them. - DoltLite source (
src/doltlite_*.c,src/prolly_*.c,src/chunk_*.c, all guarded by#ifdef DOLTLITE_PROLLY) carries no per-file license header. Match the surrounding file — do not add one. - No issue or PR numbers in code comments; those references belong in commit messages and PR descriptions.
Comments: minimal by default
Do not add explanatory comments. The comment bar in this codebase is very high — aim for effectively none. Only keep a comment that is load-bearing: one that captures a non-obvious invariant, a subtle correctness reason, or a "why it must be this way" that the code cannot express on its own. Everything else — restating what the code does, section banners, narration of obvious steps, TODO chatter — makes the code worse and must be stripped.
Agents over-comment by default; consciously resist it. Write code that reads like the surrounding DoltLite source (which is nearly comment-free), and when in doubt, leave the comment out.
Build
The build uses autosetup from a
separate build/ directory.
cd build
../configure
make doltlite # CLI shell — the prolly engine (default)
make doltlite-lib # libdoltlite.a + .so/.dylib + doltlite.h
make DOLTLITE_PROLLY=0 sqlite3 # stock SQLite, for oracle/perf comparison
- On macOS with Homebrew, link needs
LIBRARY_PATH=/opt/homebrew/lib. - The test harness runs
build/doltlite. Rebuildbuild/after anysrc/change or you validate a stale engine. A repo-root./doltliteor./sqlite3built over the prolly.ofiles is DoltLite-in-disguise; to get real stock SQLite for comparison, build withDOLTLITE_PROLLY=0in a clean directory. make lintruns the layering and raw-file-I/O guards oversrc/, and checks that SQLite-core files abovebtree.hstill match.sqlite-upstream-baseafter#ifdef DOLTLITE_PROLLYbranches are stripped.
Source layout
- Upstream-derived SQLite core —
src/*.c(parser, planner,vdbe.c,where*.c, …), kept close to upstream abovebtree.h; DoltLite-specific changes there are guarded byDOLTLITE_PROLLY. Master headersrc/sqliteInt.h. - Storage engine —
src/prolly_*.c(prolly-tree node, cursor, mutmap, diff, three-way merge, chunker, cache, hashing) andsrc/chunk_*.c(content-addressed chunk store, WAL, refs, staging, file format). - Version-control surfaces —
src/doltlite_*.c, roughly one file per feature:doltlite_commit(wire format),doltlite_commit_cmd/doltlite_add/doltlite_reset/doltlite_merge_cmd/doltlite_cherry_pick/doltlite_revert/doltlite_rebase/doltlite_config(SQL commands),doltlite_core(txn seal, catalog flush, create-commit helpers),doltlite_cmd(shared command scaffolding: option errors, peer-branch BUSY, conflict/CV txn-mode outcomes),doltlite_branch(branch/checkout),doltlite_merge(catalog orchestration indoltlite_merge, pass1/pass2 indoltlite_merge_pass1/doltlite_merge_pass2, rows indoltlite_merge_rows, schema IR indoltlite_merge_schema, plus constraint detectors indoltlite_merge_constraints/_unique/_check/_fk),doltlite_diff/doltlite_diff_stat/doltlite_diff_table,doltlite_log,doltlite_history,doltlite_blame,doltlite_tag,doltlite_conflicts,doltlite_constraint_violations/doltlite_verify_constraints,doltlite_schemas/doltlite_schema_diff,doltlite_patch,doltlite_status,doltlite_workspace,doltlite_ignore,doltlite_docs,doltlite_tests,doltlite_hashof,doltlite_gc,doltlite_remote/doltlite_http_remote/doltlite_remotesrv. Internal header:src/doltlite_internal.h; surfaces are registered insrc/doltlite.c.
The version-control surface (vtables + functions)
DoltLite exposes version control as SQLite virtual tables (system tables:
dolt_log, dolt_diff, dolt_diff_<table>, dolt_conflicts,
dolt_constraint_violations, dolt_status, …) and scalar functions / TVFs
(SELECT dolt_commit(...), SELECT dolt_merge(...), SELECT dolt_branch(...)).
Dolt exposes the same operations as stored procedures (CALL dolt_commit(...)).
This vtable form, and DoltLite-flavored column names, are intentional — not conformance bugs. When comparing against Dolt, only row-level semantics must match; do not file conformance issues over the vtable/function shape or column naming.
dolt_ignore, dolt_docs, and dolt_tests are lazy user-space system
tables: an eponymous writable vtab answers reads while no backing table
exists (SELECT never errors on a fresh repo), and the first write
statement materializes the real table, which then shadows the module.
That is Dolt's lazy-creation UX without the open-time auto-materialization
that sank the original dolt_ignore attempt (fcb7720e00). dolt_ignore
materializes
dolt_ignore(pattern TEXT NOT NULL, ignored TINYINT NOT NULL, PRIMARY KEY(pattern)).
dolt_docs materializes
dolt_docs(doc_name TEXT NOT NULL, doc_text TEXT NOT NULL, PRIMARY KEY(doc_name))
and serves a default AGENT.md row (DoltLite's own operations guide, not
Dolt's embedded text) from the module before materialization; that row is
seeded as a stored row when the table materializes. Deliberate divergences
from Dolt: the table shows in .tables/.schema once it exists; the
default AGENT.md text differs (the docs oracle compares it by name/count,
or by full row after a test overwrites it); and because the row is stored
rather than synthesized, deleting AGENT.md sticks (Dolt resurrects it on
the next read) and it appears in row-level diffs. build.c shape guards
keep hand-issued CREATE TABLE statements on the exact schema each module
creates.
dolt_tests uses the same lazy user-space system-table pattern: empty reads
resolve before a backing table exists, and the first write materializes the
six-column table with Dolt's assertion checks. dolt_test_run(...) executes
the versioned definitions as read-only single-statement queries and returns
the Dolt-compatible test result rows.
Surfaces deliberately not implemented
Absent on purpose, so don't file them as parity gaps or implement them unprompted:
-
dolt_stash— not on the roadmap. Stashing exists because git binds one working tree to a clone, so switching branches forces you to shelve uncommitted work first. DoltLite gives every branch its own working set:dolt_checkoutbetween dirty branches neither refuses nor carries changes over, and connections addressingdb/branchhold independent uncommitted state at the same time. The shelve/switch/unshelve dance has nothing to solve here. See Dolt Worktrees. -
CLI-parity wrappers around plain SQL —
dolt_rm,dolt_mv, and anything else whose whole job is to spell a statement SQL already has. These exist in Dolt so its subcommands have a server-side equivalent:dolt_rm's own source calls itself "the stored procedure for the cli command dolt rm", anddolt mvis a CLI command that buildsDROP TABLE/ rename SQL —dolt_mvis not even a Dolt procedure. DoltLite has no subcommand CLI to be compatible with, soDROP TABLEandALTER TABLE … RENAME TOare the surface. -
dolt_statistics— DoltLite already has branch-specific table statistics; they are spelledANALYZEandsqlite_stat1. Dolt keeps stats in a separate branch-keyed database that is never merged, because it maintains them automatically — continuously regenerated derived data cannot sit in the commit graph. SQLite produces statistics only when the user asks, sosqlite_stat1is an ordinary versioned table: per-branch for free, and it commits, diffs and merges with everything else. Keeping it accurate is the user's call, same asANALYZEanywhere else. -
dolt_query_catalog— DoltHub-specific storage, not engine behavior: it holds saved queries so DoltHub can display them. Nothing local reads it. Worth revisiting if pushing to DoltHub lands, and not before.
Dolt is the reference implementation
Dolt is the authority on what every
version-control operation should do. When a behavior is ambiguous, when
DoltLite and Dolt disagree, or when implementing a new VC surface, read the
Dolt source (Go) to determine the correct semantics — result shape, diff/
merge/conflict rules, dolt_* system-table columns, error conditions — and
match it. It's the reference for the oracle suites and for the engine itself.
Dolt may be checked out locally as a sibling of this repo; otherwise consult it
on GitHub.
Testing
Several independent layers. A change under src/ should run the relevant ones;
dolt on PATH is required for the oracle suites.
test/run_doltlite_tests.sh— DoltLite-native shell suites (doltlite_*.sh) covering branch/commit/diff/merge/checkout/etc. behavior and parity. Runsbuild/doltlite. This layer is separate from the regression and C tests and catches branch / default-branch / parity bugs they miss — always run it for version-control changes.test/vc_oracle_*_test.sh— Dolt oracle suites: run identical SQL against DoltLite (SELECT dolt_x) and real Dolt (CALL dolt_x) and diff the normalized output. Invoke asbash test/vc_oracle_X_test.sh build/doltlite dolt. CI auto-runs anything matchingvc_oracle_*_test.sh/oracle_*_test.sh, so a new suite with that name needs no wiring. Compare semantics, not vtable shape.- Regression buckets —
test/regression-buckets/*.txtlist the inherited and ported upstream*.testfiles gated viatestfixture.testfixtureis built withSQLITE_TEST(the CLI is not), so don't#ifdef SQLITE_TEST- guard a correctness fix, and verify fixes intestfixture, not only the CLI. Everydoltlite_*.testsuite must appear in a bucket ortest/lint_orphaned_suites.shfails. test/run_c_tests.sh— C unit tests (concurrency, crash recovery, serialize determinism, chunk-store locking, …). Locally it builds the tests it gates and flags binaries older thansrc/; under CI it runs the build phase's artifacts untouched. Either way not built and stale are counted separately from failed, so a skipped or out-of-date binary can never read as a pass.test/run_sqllogictest.sh— at full pass / 100% parity.test/known_sqllogictest_divergences.txtis empty and must stay empty: a new divergence means fix the engine, never add a known-divergence entry.
Rules that override convenience:
- Never delete a test or disable a check. If a test or assertion fails, fix the change, not the guardrail.
- For an observable bug, prove the fix with a fail-before / pass-after test (fails on the unfixed engine, passes with the fix). Latent/defensive fixes may legitimately have no such test.
- When local validation would take more than ~15 min, push a PR and let CI run the buckets/corpus/suites in parallel; spot-check locally.
PR / git workflow
- Branch off a freshly pulled
master; open the PR againstmaster. Do not stack PRs — a stacked branch leaves commits dangling when its base merges first; rebase ontomasterinstead. - Stage by explicit path (
git add src/foo.c test/bar.sh).git add -A/git add .sweep in hundreds of untracked build artifacts (*.o,tsrc/*.c,./doltlite,./sqlite3,testfixture). - Commit or push only when asked. End commit messages and PR bodies with the trailers the harness expects.
Version-control correctness invariants
- Conflicts are never persisted. A conflicted merge lives only in the
transaction that produced it:
COMMITis refused while any conflict remains, an autocommit merge that conflicts is rolled back whole, and the persist path declines to make a conflicted working set durable. Nothing conflicted reaches disk, so no later connection inherits a merge to finish. Dolt allows this behind@@dolt_allow_commit_conflicts, which DoltLite does not implement, so conflict persistence is a deliberate divergence — compare in-transaction behaviour against Dolt, not what survives a commit. Constraint violations are unaffected and still persist.
Subtle rules that are easy to break silently — hold them when touching VC code:
- Re-confirm under the lock. Multi-step ops (merge / cherry-pick / revert / pull) must re-read HEAD under the lock immediately before advancing a ref, or they clobber a concurrent peer.
- Scoped ref installs. A pushed ref update must go through the scoped-refs validator (only the declared branch may change); never install a pushed blob wholesale. Clone install is unscoped by design.
- Canonical catalog. Schema commits adopt the canonical catalog; the live
catalog must always equal the persisted one. Constructed catalog arrays
(staging / merge /
-am) adopt the single master root paired byiTable==1, never by name. - Ref resolution for commit / branch /
HEAD/WORKING/STAGEDgoes throughdoltliteResolveCatalogHashForRef— reuse it, don't re-derive.
Documentation
Update user-facing documentation only when the user explicitly requests documentation work. A bug fix, feature, refactor, or observable behavior change does not by itself authorize documentation edits. Explain the change in the PR and add regression coverage; do not turn each fix into another paragraph in a guide or a new published contract claim.
Keep existing contract evidence accurate when tests or implementation change. This does not require expanding documentation or adding contract rows for every regression test. When documentation work is requested, keep the affected docs, contract claims, and evidence consistent.
Concurrency contract
User-facing concurrency guarantees and the multiproc/multi-connection oracles
that pin them live in doc/doltlite/concurrency.md and
test/concurrency_contract.tsv. test/concurrency_contract_test.sh fails if a
claim loses its evidence needle. When changing graph lock, snapshot pin or
write upgrade, locked ref expectation/CAS paths, multiproc harnesses, or
conflict durability, update the TSV and keep the multiproc C suites green
(multi_process_*, concurrent_* via test/run_c_tests.sh).
The version-control correctness invariants above remain load-bearing for implementors even when a claim is also listed in the contract.
SQLite compatibility contract
The user-facing SQLite compatibility contract lives in
doc/doltlite/sqlite-compatibility.md; claims and evidence live in
test/sqlite_compatibility_contract.tsv. Maintain existing contract evidence
when affected by a change; follow the documentation policy above for doc edits
and new published claims.
Do not infer poor SQLite compatibility from an intentional storage-engine
adaptation. DoltLite uses chunks rather than pages, has no SQLite WAL or
rollback journal, and cannot use an anonymous rowid as a version-controlled
key because identity must be history-independent. Classify inherited-suite
exceptions through test/known_testfixture_divergences.txt: engine-gap is a
bug to fix, while intentional, unsupported, and harness entries describe
known boundaries. SQLLogicTest remains a zero-divergence correctness gate.
Storage format version
Chunk-store version 12 is the beta format freeze. User-facing rules live in
doc/doltlite/storage-format.md; claims and evidence live in
test/storage_format_contract.tsv, and the golden file lives under
test/format-corpus/v12/.
Bumping CHUNK_STORE_VERSION requires:
- A new
test/format-corpus/entry and MANIFEST storage_format_contract.tsvevidence update- Explicit open/upgrade policy for version 12 (open-only, migrate, or refuse),
described in the PR; update
doc/doltlite/storage-format.mdwhen documentation work is explicitly requested
Any incompatible change to a nested write format, including working sets,
catalogs, refs, commits, prolly nodes, or key encoding, must bump
CHUNK_STORE_VERSION. Do not silently reinterpret another version.
The release workflow also runs test/doltlite_compat_test.sh against prior
releases. Run it when changing format recognition, readers, writers, or version
handling; version-12 databases already created by Beta users are compatibility
fixtures, not disposable test data.
Beta release surfaces
DoltLite is public Beta. Move quickly on engine fixes, but treat documented
behavior, version-12 data, the compatibility and concurrency contracts, public
C headers and exports, release packages, and binding inputs as shipped
surfaces. A change under packaging/, the install/export rules, amalgamation
generation, or .github/workflows/release.yml must run the relevant package or
artifact smoke test; core engine tests alone do not validate a distributed
artifact.
External integration and documentation work is no longer a pre-Beta deprioritized category. Scope validation to the surface being changed, preserve the published contracts, and fix the engine with fail-before/pass-after evidence when behavior is wrong.