A branch is transaction time with a tree order.

Macrame is an embedded bitemporal ledger in Rust above unmodified libSQL. Parallel recorded histories share one valid-time domain: forked transaction time, resolved by rule, measured at 0.15 ms forks and depth-independent reads.

cargo add macrame-db pip install macrame-db

0.19.1 on crates.io and PyPI. One file, no server — the manual opens the ledger in a page.

Two clocks and a lineage: valid time (when a fact held) and recorded time (when the database learned it), plus which line of inquiry recorded it. No third axis: eight binding doctrines hold the design below.

Choose a lineage and follow its root path main root b1 fork t1 b2 fork t2 L fork t3 sib fork t2′ t1 ≤ t2 ≤ t3 down every path

Read on L: root path L → b2 → b1 → main

Each ancestor contributes only rows recorded at or before its fork clamp. Among the holders of a key, the nearest to L wins, deterministically, from stored state alone.

Choose a lineage to trace its root path. Fork times never decrease from parent to child; valid time stays shared across every branch.

Everyone branched something else.

Every line of work branches something: worlds, states, commits, or the valid future. Macrame is the exception: it branches the transaction-time order, keeps valid time separate, and resolves reads by a rule in the model. A 2026 PRISMA review of 54 bitemporal studies records branching exactly once, on the valid axis. Nothing branches the record.

Line of workBranchesTransaction-time orderCross-branch resolution
Bitemporal foundations (SQL:2011, BCDM)—singlen/a
Branching-time modelsvalid futuresingle—
Probabilistic / what-if (MayBMS)possible worldssingle—
Dataset versioning (Decibel, OrpheusDB)dataset state—merge at materialization
Git-for-data (Dolt)commitscommit orderAS OF → ancestry
Macrame (this work)txn-time order (tree)forked, partial ordernearest-ancestor, deterministic

Eight doctrines, before and after.

Binding constraints, not guidelines — checked by the stability suite, changed only through dated amendments. They drew the storage, the interface, and the refusals: append-only rows, a trigger-captured log, disposable caches, and signatures that declare their time.

  1. The boundary is sacred

    Everything above the libSQL connection is project code; the engine is never patched, never forked.

  2. Two clocks, never mixed

    Every row carries valid and transaction time; no path may derive one from the other. Lineage is a clause on the second clock.

  3. Assertions are immutable

    Rows are never updated in place; a new recorded state is a new insert with a fresh recorded_at.

  4. The ledger is a table, not the log

    Reconstruction reads transaction_log, captured by engine triggers, never the WAL, replication frames, or CDC.

  5. No physical deletion in primary tables

    Rows leave only through a declared, verified archive session; ad-hoc DELETE aborts at the trigger layer.

  6. Derivative state is disposable

    links_current is a cache of current recorded state, rebuildable by one deterministic query; audit detects drift, rebuild recovers.

  7. Embeddings are immutable per version, excluded from the ledger

    Vectors live in per-model tables, never in log payloads; vector search is present-state only.

  8. Fidelity is a parameter, never a silent default

    Queries mixing time dimensions declare it in their signatures; the past's text is never attached silently.

Three questions, three reads.

A bitemporal database answers two questions. Macrame adds the third (what did this line of inquiry believe?) as sequenced modifiers in builder form, never as one parameter with two meanings.

Three read modes: true at v, believed at r, believed by L Which question? true at v as_of_valid(v) L’s current state believed at r as_of_recorded(r) fold the log to r believed by L reconstruct_on(r, L) fold with ancestry believed at r about true at v compose compose
Set both cutoffs to ask what the ledger believed at recorded time r about what was true at valid time v. Each lineage read follows one ancestry; comparing what L holds that M does not is outside the model.
  1. as_of_valid(v)True at v — valid time under current belief. No branch predicate: branches share the valid-time domain, so no valid-time query changes meaning.
  2. as_of_recorded(r)Believed at r — the log folded to r. An unstated attribute mode refuses rather than defaulting (Doctrine VIII).
  3. reconstruct_on(r, L)Believed by L — the fold scoped by ancestry, nearest ancestor winning. Resolution is silent: one position per key, no flag where a sibling disagrees.

Compose the instants: as_of_valid(v) + as_of_recorded(r) asks believed at r about true at v: current belief about the past routinely differs from past belief about the past.

import datetime, macrame
db = macrame.Database.open("ledger.db")
tuesday = datetime.datetime(2026, 3, 3, tzinfo=datetime.timezone.utc)
past = db.load_subgraph(
    "invoice-7", 3, 1 << 20,
    as_of_recorded=tuesday,
    attribute_mode=macrame.AttributeMode.AT_TIME,
)  # Tuesday’s topology wearing Tuesday’s titles

A stratum, not a fork.

Schema generation, triggers, and temporal logic live above the engine interface, project code over unmodified libSQL. Every guarantee is SQL the engine executes, portable to any engine that runs it. Doctrine I restates a 1998 thesis as a project boundary, and §5 settles the layering dispute with measurements rather than adjectives.

Temporal and lineage logic above unmodified libSQL API: as_of_valid, as_of_recorded, reconstruct_on temporal + lineage reads logic: single-writer Write Actor temporal and lineage resolution DDL: schema, triggers, query compilation the layer the write path cannot bypass the libSQL connection: Doctrine I boundary unmodified libSQL: never patched, never forked SQLite storage engine, WAL
Project code owns schema, temporal rules, and lineage resolution above unmodified libSQL. Triggers enforce immutable assertions, capture the transaction log, and reject physical deletion outside archive sessions.

One write, three rows, zero rewrites.

A single edge assertion produces three writes: the ledger row, its current-state upsert, and its log entry. Rows are never updated in place: a new recorded state is a new insert, so retroactive correction is an operation, not an accident.

One assertion flows through the ledger, log, and cache Write Actor sole writer links append-only, III transaction_log trigger-captured, IV links_current disposable cache, VI ad-hoc DELETE aborts at trigger, V as_of_valid filtered read as_of_recorded fold to r, reconstruct_on
Overlap is resolved by the single writer, avoiding a check-then-write race. Triggers capture the log and project current state; ad-hoc deletion is rejected. Concepts inherit across lineages, never restated.

One file, two clocks, and the tooling around them.

The model is the headline; the system around it is the reason it survives contact with real work.

  1. Embedded, no serverOne libSQL file, one writer, WAL readers unblocked. Branch, fold, and archive against the file in place — there is nothing to operate.
  2. Rust core, Python bindingsTraversals, belief-at-instant reads, snapshots, and the archive from either side of the boundary — and the graph algorithms run on loaded subgraphs.
  3. History you can auditEvery superseded row stays in the ledger. Reconstruction replays the trigger-captured log, snapshots anchor the trunk, and archive sessions move cold rows with verifiable reports.
  4. Branches for real workCompeting extractions, corrections under review, planning scenarios — each lineage keeps its recorded positions, resolved by rule on read instead of merged by convention. The sharpest case: a correction authored Monday, reviewed Wednesday, adopted Friday — recorded as a candidate before it becomes the accepted position.
  5. Integrity by constructionOverlap guards, lineage provenance, and deletion refusal live in triggers and the single writer. Callers inherit the guarantees; they are never asked to keep them.

Around the ledger sits the rest of the system: snapshots that anchor trunk reconstruction; archive sessions that move cold rows, blobs with them, under verifiable reports; a content-addressed blob store reclaimed through the archive instead of by reference counting; application attributes riding the concept row as versioned JSON with expression indexes over their paths; a branch-global key-value sidecar for the operational state the ledger should never see; full-text search beside present-state vector search; and communities, shortest paths, and dense cores running on loaded subgraphs. Per-shape version gates keep older binaries folding what newer ones wrote.

Settled by measurement.

One harness, reference hardware, trigger amplification included. Read shapes and orders of magnitude here (flat across sizes, constant across depths), not decimals.

OperationFigureBudget / baseline
Single assertion224 µs, flat to 8,000 edgeswrong-index defect removed
Chunk commit, triggers in~2.35 ms3 ms bound
Three-hop traversal1.66 ms10 ms budget
Branched read1.1–1.3× unfiltered, depth-independent to 100ancestry materializes once, joins per hop
Fork0.15 ms, O(1) — one register rowno inherited fact is written
Forked replay~3 ms flat, from the initial statesnapshots carry no recorded_at for a cutoff

BranchBench illustrates a tradeoff: fast forks can bring increasingly expensive reads as histories deepen. Macrame’s measurements show a different scaling profile: trunk operations remain unchanged, forks add one register row, and branch reads stay depth-independent through 100 levels.

Deliberate boundaries.

Every system is a set of refusals. Each boundary below is stated with the condition that would reopen it. None of them is a roadmap promise.

  • Divergence is modeled; convergence is not. No rule composes two lineages' recorded positions into one — by design, since a merge rule that silently picks winners would violate the integrity the refusals protect. Ancestry-determined precedence is the outlined form of the missing rule.
  • Inheritance without restatement. Branches inherit concepts but cannot restate them, so retiring an inherited concept has no branch-local operation — the deferred overlay design stands until its reopening symptoms appear.
  • Replays fold from the initial state. A snapshot without recorded_at cannot serve a lineage-scoped cutoff, so closing this gap means snapshotting per lineage or not at all, with the storage costs computed first.
  • Similarity lives in the present. The embedding index derives from current recorded state and crosses no lineage; temporal-vector consistency is stated as a limit: to our knowledge an unclaimed gap anywhere.

Forked recorded histories, carried by a trigger layer above an unmodified engine, at a small depth-independent overhead.

Macrame 0.19.1 — schema v22, concept payload v3, snapshot v5. Embedded Rust with Python bindings: traversals, branch-scoped reads, snapshots, the archive, and the algorithms that run on the graph.

macrame-db 0.19.1 · schema v22 · MSRV 1.88