Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/idric-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
- 'docs/idric-implementation.md'
- 'docs/developer-workbench.md'
- 'docs/storage-model.md'
- 'docs/vector-index.md'
- '.github/workflows/idric-core.yml'

permissions:
Expand Down Expand Up @@ -107,3 +108,8 @@ jobs:
grep -Fx 'copy-label=Copy' /tmp/ib-smoke.txt
grep -Fx 'copy-target=code-1' /tmp/ib-smoke.txt
grep -Fx 'copy-payload=git status' /tmp/ib-smoke.txt
grep -Fx 'vector-backend=flat-f32-exact' /tmp/ib-smoke.txt
grep -Fx 'vector-scalar=f32' /tmp/ib-smoke.txt
grep -Fx 'vector-directory=indexes/vectors/pages/test-model' /tmp/ib-smoke.txt
grep -Fx 'vector-format-readable=True' /tmp/ib-smoke.txt
grep -Fx 'vector-bytes-readable=False' /tmp/ib-smoke.txt
84 changes: 84 additions & 0 deletions .github/workflows/vector-index.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Filesystem vector index

on:
pull_request:
paths:
- 'native/vector-index/**'
- 'tests/vector-index-smoke.sh'
- 'src/IB/VectorIndex.idric'
- 'src/IB/Storage.idric'
- 'docs/vector-index.md'
- '.github/workflows/vector-index.yml'

permissions:
contents: read

jobs:
host:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Compile warning-clean C99 backend
run: make -C native/vector-index

- name: Exercise build, persistence, validation, and exact query
run: make -C native/vector-index test

- name: Exercise the 10,000 URL scale with Float32 storage
run: |
index_root="$(mktemp -d)/state/indexes/vectors/pages/scale-smoke"
awk 'BEGIN {
for (row = 0; row < 10000; row++) {
printf "url-%d\t", row
for (column = 0; column < 384; column++)
printf "%s0.01", column == 0 ? "" : " "
printf "\n"
}
}' | native/vector-index/ib-vector-index build "$index_root" 384 cosine | tee /tmp/vector-scale-build.txt
grep -Fx 'count=10000' /tmp/vector-scale-build.txt
vector_file="$(sed -n 's/^vectors //p' "$index_root/format.txt")"
test "$(stat -c %s "$index_root/$vector_file")" = 15360000
awk 'BEGIN {
for (column = 0; column < 384; column++)
printf "%s0.01", column == 0 ? "" : " "
printf "\n"
}' | native/vector-index/ib-vector-index query "$index_root" 3 > /tmp/vector-scale-query.txt
test "$(wc -l < /tmp/vector-scale-query.txt)" = 3
sed -n '1s/\t.*//p' /tmp/vector-scale-query.txt | grep -Fx 'url-0'

android:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- abi: arm64-v8a
compiler: aarch64-linux-android24-clang
file_architecture: ARM aarch64
- abi: armeabi-v7a
compiler: armv7a-linux-androideabi24-clang
file_architecture: ARM
steps:
- uses: actions/checkout@v4

- uses: android-actions/setup-android@v3

- name: Install pinned Android NDK
run: sdkmanager 'ndk;27.2.12479018'

- name: Cross-compile small Android executable
run: |
toolchain="$ANDROID_SDK_ROOT/ndk/27.2.12479018/toolchains/llvm/prebuilt/linux-x86_64/bin"
mkdir -p build/android
"$toolchain/${{ matrix.compiler }}" \
-O2 -Wall -Wextra -Werror -Wpedantic -std=c99 \
native/vector-index/ib_vector_index.c -lm \
-o build/android/ib-vector-index
"$toolchain/llvm-strip" build/android/ib-vector-index
file build/android/ib-vector-index | grep -F '${{ matrix.file_architecture }}'
test "$(stat -c %s build/android/ib-vector-index)" -lt 100000

- uses: actions/upload-artifact@v4
with:
name: ib-vector-index-android-${{ matrix.abi }}
path: build/android/ib-vector-index
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/build/
/native/vector-index/ib-vector-index
1 change: 1 addition & 0 deletions docs/idric-implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The initial source modules deliberately keep the executable boundary small:
- `IB.Index` builds transparent rebuildable list indices without collapsing duplicate visits.
- `IB.Storage` classifies schema-shaped paths and defines which records may be generically inspected.
- `IB.FileStore` performs the first real browser-owned file I/O: it creates the store/tab directories, reads and writes tab manifests, appends history records, rejects paths outside the canonical schema, and reports filesystem failures.
- `IB.VectorIndex` owns the replaceable vector-backend specification and lowers it to the versioned text-stream process contract. The first backend is the filesystem-native exact `float32` tool in `native/vector-index`.
- `IB.Inspect` summarizes physical rows without following or interpreting renderer state.

The first slice does not embed Python, Ithon, WebView UI, or a renderer. Chrome/Firefox SQLite import and Android filesystem walking are platform adapters to add around this core, not reasons to move the core out of Idriç.
Expand Down
2 changes: 2 additions & 0 deletions docs/storage-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ or

For tens of thousands of tabs, the first implementation can remain intentionally simple. If scans become expensive, an SQLite or custom index can be introduced without changing the canonical tab model.

The first vector implementation follows the same rule without SQLite: inspectable `format.txt` and ID files point to row-major `float32` vector bytes under `indexes/vectors/`. It uses exact scanning at the 10,000-URL scale and exposes a backend-neutral streaming command contract, so an approximate implementation can replace it without changing canonical records. See `vector-index.md`.

## Sync

Sync should operate on the browser-owned records and snapshots, not on a renderer profile directory. This allows multiple browser front ends or machines to share the same durable browsing corpus while maintaining separate live renderer processes and caches.
61 changes: 61 additions & 0 deletions docs/vector-index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Filesystem vector index

IB's first vector backend is a flat exact scan over 32-bit floats. It is deliberately a file tool, not a database.

At the initial 10,000-URL workbench size, 384-dimensional vectors occupy 15.4 MB and one exact query performs 3.84 million multiply-adds. A graph index would add persistent graph state, tuning, and approximate results before this corpus needs them.

## Boundary

Canonical URLs, visits, extracted text, and document identities remain ordinary inspectable browser records. Embeddings and their index are derived state and can be deleted and rebuilt.

Idriç owns the index specification: collection, embedding model, dimensions, metric, and selected backend. `IB.VectorIndex` lowers that specification to a versioned process contract. Grease or thin platform glue may run the selected program. The initial program is `ib-vector-index`, implemented in C99 so the same source builds for Linux and the Android NDK.

A replacement backend must implement the same standard-input/standard-output contract:

```text
BACKEND build INDEX_DIRECTORY DIMENSIONS cosine|dot < rows.tsv
BACKEND query INDEX_DIRECTORY RESULT_COUNT < vector.txt
BACKEND check INDEX_DIRECTORY
BACKEND inspect INDEX_DIRECTORY
```

Build input has one row per line:

```text
document-id<TAB>0.1 -0.2 0.3 ...
```

Query input is one space-separated vector. Query output is score-descending text:

```text
document-id<TAB>0.8125
```

This interface does not expose the flat backend's private files. A later USearch or HNSW program can occupy the same boundary without changing canonical browser state or the callers that stream rows and queries.

## Files

An index lives below the existing derived namespace:

```text
state/indexes/vectors/<collection>/<embedding-model>/
format.txt
ids-<generation>.txt
vectors-<generation>.f32
```

`format.txt` is the atomic pointer to one immutable generation. It records the contract version, backend, scalar, byte order, metric, dimensions, row count, and current data filenames. IDs remain text. Vectors are row-major little-endian IEEE 754 `float32` values.

New builds write new generation files and replace `format.txt` last. A failed build therefore cannot make a partial generation current. Old generation files may be removed during serialized index maintenance after active queries finish.

The generic inspector may read `format.txt` and generated ID text. It classifies the vector bytes as derived but does not treat them as generic readable text.

## Metric and precision

`cosine` normalizes stored and query vectors once and then uses a dot product. Zero, NaN, and infinite vectors are rejected. `dot` stores the supplied values without normalization.

Storage and accumulation both use 32-bit float. This is the natural precision of common embedding outputs, halves the vector bytes relative to doubles, and is sufficient for similarity ranking here. The rebuildable boundary lets a later backend use another representation without migrating canonical data.

## Growth path

The flat scan is also the correctness reference for any approximate replacement. Add an ANN backend only after measurements on the phone show that exact query latency or corpus size is actually a problem. USearch is the leading replacement candidate because it has a C API, Android support, `f32`, and disk-backed index viewing, but it is not a dependency of this first backend.
17 changes: 17 additions & 0 deletions native/vector-index/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CC ?= cc
CFLAGS ?= -O2
CPPFLAGS ?=
WARNINGS = -Wall -Wextra -Werror -Wpedantic

.PHONY: all clean test

all: ib-vector-index

ib-vector-index: ib_vector_index.c
$(CC) $(CPPFLAGS) $(CFLAGS) $(WARNINGS) -std=c99 $< -lm -o $@

test: ib-vector-index
sh ../../tests/vector-index-smoke.sh ./ib-vector-index

clean:
rm -f ib-vector-index
Loading
Loading