Scriptum

Full-text search with branching

Scriptum adds copy-on-write branches to Apache Lucene. A fork shares immutable segment files with its source, while later writes remain isolated. Readers can also open earlier commit points.

Why Scriptum

  • Metadata-only forks: Branch an index in a few milliseconds by copying metadata rather than segment data.
  • Structural sharing: Branches share immutable Lucene segments via copy-on-write overlay directories.
  • Time travel: Open readers at any historical commit point. Query past index states.
  • Lucene 10.x: Use Lucene text search, KNN vectors, facets and highlighting within each branch.
  • Apache-2.0: Open source, permissive license.

How it works

Scriptum adds four components around Lucene's existing index APIs:

  • BranchedDirectory: Overlay pattern: reads fall back to base, writes go to branch overlay.
  • BranchDeletionPolicy: Retains all commit points until explicit garbage collection.
  • BranchAwareMergePolicy: Prevents merging shared segments that would break other branches.
  • BranchIndexWriter: Main API for create, fork, commit, merge, and GC operations.

See the Lucene extension guide for implementation details.

Clojure API

What is this syntax?
require('[scriptum.core :as sc])

;; Create an index
def writer: sc/create-index("/tmp/my-index")

;; Add documents
sc/add-doc(writer,
  {:title {:type :text, :value "Hello World"},
   :id {:type :string, :value "doc-1"}})
sc/commit!(writer, "Initial commit")

;; Fork a branch (3-5ms regardless of index size)
def experiment: sc/fork(writer, "experiment")

;; Add to branch (doesn't affect main)
sc/add-doc(experiment,
  {:title {:type :text, :value "Branch only"},
   :id {:type :string, :value "doc-2"}})
sc/commit!(experiment, "Added experimental doc")

;; Main still has 1 doc, branch has 2
count(sc/search(writer, {:match-all {}}, 100))
count(sc/search(experiment, {:match-all {}}, 100))

;; Merge back when ready
sc/merge-from!(writer, experiment)
(require '[scriptum.core :as sc])

;; Create an index
(def writer (sc/create-index "/tmp/my-index"))

;; Add documents
(sc/add-doc writer {:title {:type :text :value "Hello World"}
                    :id    {:type :string :value "doc-1"}})
(sc/commit! writer "Initial commit")

;; Fork a branch (3-5ms regardless of index size)
(def experiment (sc/fork writer "experiment"))

;; Add to branch (doesn't affect main)
(sc/add-doc experiment {:title {:type :text :value "Branch only"}
                        :id    {:type :string :value "doc-2"}})
(sc/commit! experiment "Added experimental doc")

;; Main still has 1 doc, branch has 2
(count (sc/search writer {:match-all {}} 100))      ;; => 1
(count (sc/search experiment {:match-all {}} 100))  ;; => 2

;; Merge back when ready
(sc/merge-from! writer experiment)

Java API

import org.replikativ.scriptum.BranchIndexWriter;
import org.apache.lucene.document.*;
import java.nio.file.Path;

// Create an index
BranchIndexWriter main = BranchIndexWriter.create(
    Path.of("/tmp/my-index"), "main");

// Add documents
Document doc = new Document();
doc.add(new TextField("title", "Hello World", Field.Store.YES));
main.addDocument(doc);
main.commit("Initial commit");

// Fork in a few ms regardless of index size
BranchIndexWriter feature = main.fork("experiment");

// Branches evolve independently
Document branchDoc = new Document();
branchDoc.add(new TextField("title", "Branch only", Field.Store.YES));
feature.addDocument(branchDoc);
feature.commit("Feature work");

// Merge back
main.mergeFrom(feature);

When to use Scriptum vs Proximum

Scriptum

Full-text search with Lucene

  • Keyword search, facets, highlighting
  • Text analysis pipelines
  • Document-oriented indices
  • When you need Lucene's query language

Proximum

Vector similarity search

  • Embedding-based retrieval (RAG)
  • Semantic search
  • Dedicated HNSW vector indexing
  • Java and Clojure vector-store APIs

Both have branching, snapshots, and time-travel. Choose based on your search workload.

Requirements

  • Java 21+: Required for Lucene 10.x (Foreign Memory API, Vector API)
  • Lucene 10.3.2: Pulled from Maven Central
  • Clojure 1.12.0+: For the Clojure API

Install

Available on Clojars. See the GitHub repository for current version and installation instructions.

Maven/Gradle users: add the Clojars repository to your build configuration.