https://github.com/zd87pl/loci-db
4D embeddings DB for world models
https://github.com/zd87pl/loci-db
Last synced: 13 days ago
JSON representation
4D embeddings DB for world models
- Host: GitHub
- URL: https://github.com/zd87pl/loci-db
- Owner: zd87pl
- License: apache-2.0
- Created: 2026-03-13T12:32:05.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-04-05T03:28:08.000Z (22 days ago)
- Last Synced: 2026-04-05T05:13:33.399Z (22 days ago)
- Language: Python
- Size: 270 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Roadmap: ROADMAP.md
Awesome Lists containing this project
README
# LOCI
**A 4D spatiotemporal vector database for AI world models.**
[](https://github.com/zd87pl/loci-db/actions)
[](https://pypi.org/project/loci-stdb/)
[](https://www.python.org/downloads/)
[](LICENSE)
---
## The Problem
Modern world models — V-JEPA 2, DreamerV3, GAIA-1, UniSim — produce embeddings
where every vector has an implicit **4D spatiotemporal address** `(x, y, z, t)`.
Existing vector databases (Qdrant, Milvus, Weaviate) treat all embedding dimensions
equally: a spatial query requires 3+ float-range payload filters evaluated
independently, time-based retrieval has no native sharding, and there is no
concept of "predict the future then find what's nearby."
## The Solution
LOCI is a middleware layer on top of [Qdrant](https://qdrant.tech) that makes
spatiotemporal structure **first-class** through three novel primitives:
### 1. Multi-Resolution Hilbert Bucketing
Encode `(x, y, z, t)` at multiple Hilbert resolutions (p=4, 8, 12).
Spatial bounding-box queries use a Hilbert integer pre-filter with overlap, then
apply an exact payload post-filter as the authoritative geometric check. By
default queries start at the coarsest indexed resolution; with `adaptive=True`,
dense regions can be promoted to finer Hilbert resolutions at query time.
```
Naive Qdrant LOCI
┌──────────────────┐ ┌──────────────────┐
│ x_min ≤ x ≤ x_max│ │ │
│ y_min ≤ y ≤ y_max│ → │ hilbert_r4 ∈ {…} │
│ z_min ≤ z ≤ z_max│ │ (single filter) │
└──────────────────┘ └──────────────────┘
```
### 2. Temporal Sharding
Automatic routing of vectors to **time-partitioned Qdrant collections**
(`loci_{epoch_id}`). Configurable epoch size. Queries fan out only to
epochs that overlap the requested time window — with the async client,
all shards are searched **concurrently** via `asyncio.gather`.
### 3. Predict-then-Retrieve with Novelty Detection
An **atomic API call** that composes a user-supplied world model with
vector search, returning both results and a **novelty score**:
```python
result = client.predict_and_retrieve(
context_vector=current_embedding,
predictor_fn=my_world_model,
future_horizon_ms=2000,
current_position=(0.5, 0.3, 0.8),
)
print(f"Novelty: {result.prediction_novelty:.2f}")
# 0.0 = "I've seen this before"
# 1.0 = "This is new territory"
```
## Quick Start
### Quick Start with Docker
The fastest way to run LOCI with a persistent Qdrant backend:
```bash
docker compose up
```
This starts two services:
- **loci** — the LOCI REST API on `http://localhost:8000`
- **qdrant** — the Qdrant vector store on `http://localhost:6333`
Qdrant data is persisted in a named volume so it survives restarts.
Once running, insert and query world states via the HTTP API:
```bash
# Health check
curl http://localhost:8000/health
# Insert a world state (512-dim vector)
curl -X POST http://localhost:8000/insert \
-H 'Content-Type: application/json' \
-d '{"x":0.5,"y":0.3,"z":0.8,"timestamp_ms":1700000000000,"vector":[0.1],"scene_id":"s1"}'
# Query (spatial + time window)
curl -X POST http://localhost:8000/query \
-H 'Content-Type: application/json' \
-d '{"vector":[0.1],"x_min":0.0,"x_max":1.0,"limit":10}'
```
Interactive API docs: `http://localhost:8000/docs`
---
### No Docker? No problem — in-memory mode
Try LOCI instantly with zero infrastructure using `LocalLociClient`:
```bash
pip install loci-stdb # or: pip install -e ".[dev]"
```
```python
from loci import LocalLociClient, WorldState
client = LocalLociClient(vector_size=512)
# Insert a world state
state = WorldState(
x=0.5, y=0.3, z=0.8,
timestamp_ms=1000,
vector=[0.1] * 512,
scene_id="my_scene",
)
state_id = client.insert(state)
# Query by vector similarity + spatial bounds + time window
results = client.query(
vector=[0.1] * 512,
spatial_bounds={"x_min": 0.0, "x_max": 1.0,
"y_min": 0.0, "y_max": 1.0,
"z_min": 0.0, "z_max": 1.0},
time_window_ms=(0, 5000),
limit=10,
)
```
### With Qdrant (production)
```bash
pip install loci-stdb
docker run -p 6333:6333 qdrant/qdrant
```
```python
from loci import LociClient, WorldState
client = LociClient(
"http://localhost:6333",
vector_size=512,
epoch_size_ms=5000,
distance="cosine",
)
# Insert world states
state = WorldState(
x=0.5, y=0.3, z=0.8,
timestamp_ms=1700000000000,
vector=[0.1] * 512,
scene_id="warehouse_sim",
scale_level="patch",
)
state_id = client.insert(state)
# Batch insert (truly batched — one Qdrant call per epoch)
ids = client.insert_batch(states)
# Spatiotemporal query with overlap factor
results = client.query(
vector=query_embedding,
spatial_bounds={"x_min": 0.2, "x_max": 0.8,
"y_min": 0.0, "y_max": 1.0,
"z_min": 0.0, "z_max": 1.0},
time_window_ms=(start_ms, end_ms),
limit=10,
overlap_factor=1.2, # 20% expanded search for boundary recall
)
# Predict-then-retrieve with novelty scoring
result = client.predict_and_retrieve(
context_vector=current_embedding,
predictor_fn=my_world_model,
future_horizon_ms=2000,
current_position=(0.5, 0.3, 0.8),
)
# Trajectory reconstruction via scroll API
trajectory = client.get_trajectory(state_id, steps_back=20, steps_forward=20)
# Episodic context window
context = client.get_causal_context(state_id, window_ms=5000)
```
### Async API (parallel shard fan-out)
```python
from loci import AsyncLociClient
async with AsyncLociClient(
"http://localhost:6333",
vector_size=512,
distance="cosine",
) as client:
await client.insert(state)
results = await client.query(vector=query_embedding, limit=10)
```
### World Model Adapters
```python
from loci.adapters.vjepa2 import VJEPA2Adapter
from loci.adapters.dreamer import DreamerV3Adapter
from loci.adapters.generic import GenericAdapter
# V-JEPA 2
adapter = VJEPA2Adapter()
states = adapter.batch_clip_to_states(clip_output, ts, scene_id)
# DreamerV3
adapter = DreamerV3Adapter()
ws = adapter.rssm_to_world_state(h_t, z_t, position, ts, scene_id)
# Generic numpy/torch
adapter = GenericAdapter(expected_dim=512)
ws = adapter.from_numpy(embedding, position, ts, scene_id)
```
## Performance
**Raw spatiotemporal query latency: ~75µs p50** (label-filtered, 100 objects, 128-dim, Apple Silicon).
| N objects | Query type | P50 | P99 |
|--:|:--|--:|--:|
| 100 | Label-filtered (demo path) | 75µs | 124µs |
| 100 | Vector-only ANN | 212µs | 217µs |
| 100 | Temporal shard pruning | 156µs | 188µs |
| 500 | Label-filtered (demo path) | 259µs | 281µs |
| 1,000 | Label-filtered (demo path) | 469µs | 514µs |
| 1,000 | Vector-only ANN | 1.86ms | 2.08ms |
Insert throughput: **~59,000 states/s** (in-memory backend, 128-dim vectors).
Run the retrieval benchmark on your hardware:
```bash
python benchmarks/benchmark_retrieval.py
```
For a LOCI-vs-naive-Qdrant comparison benchmark:
```bash
# In-memory (no Qdrant server needed):
python benchmarks/vs_naive_qdrant.py
# Against a live Qdrant server:
QDRANT_URL=http://localhost:6333 python benchmarks/vs_naive_qdrant.py
```
Results are written to `benchmarks/results/` and printed as markdown tables.
## Why not SpatCode?
SpatCode (WWW 2026, arXiv 2601.09530) encodes coordinates into the embedding
space for soft/fuzzy retrieval via RoPE-style positional encoding. LOCI uses
Hilbert bucketing for **exact geometric range queries** with deterministic behavior.
**Use SpatCode** when semantic proximity matters (e.g., "find images taken
near this location").
**Use LOCI** when physical boundaries matter (e.g., "find all observations
within this 3D bounding box in the last 5 seconds").
## Why not TANNS?
TANNS (ICDE 2025) builds a single graph managing all timestamps internally
with a Timestamp Graph structure. LOCI uses collection-level sharding with
storage tiering.
**Use TANNS** for single-session temporal ANN where all data fits in one graph.
**Use LOCI** when you need cross-session persistence, multi-agent memory sharing,
hot/warm/cold storage tiering, or predict-then-retrieve.
## Architecture
```
┌───────────────────────────────────────────────┐
│ Application Layer │
│ LociClient / AsyncLociClient / LocalLociClient│
│ insert · query · predict_and_retrieve │
├───────────────────────────────────────────────┤
│ Retrieval Layer │
│ predict.py — predict-then-retrieve + novelty │
│ funnel.py — multi-scale coarse→fine search │
├───────────────────────────────────────────────┤
│ Indexing & Routing Layer │
│ spatial/ — multi-res Hilbert + overlap │
│ temporal/ — epoch sharding + decay scoring │
├───────────────────────────────────────────────┤
│ Adapters Layer │
│ V-JEPA 2 · DreamerV3 · Generic numpy/torch │
├───────────────────────────────────────────────┤
│ Storage Layer │
│ Qdrant (one collection per temporal epoch) │
│ MemoryStore (in-process, no infra needed) │
└───────────────────────────────────────────────┘
```
See [ARCHITECTURE.md](ARCHITECTURE.md) for the full design document.
## Documentation
- [ARCHITECTURE.md](ARCHITECTURE.md) — System design
- [docs/NOVELTY.md](docs/NOVELTY.md) — Novelty claims vs prior art
- [docs/BENCHMARK_METHODOLOGY.md](docs/BENCHMARK_METHODOLOGY.md) — Benchmark replication guide
- [docs/WORLD_MODEL_INTEGRATION.md](docs/WORLD_MODEL_INTEGRATION.md) — Integration guides
## Development
```bash
git clone https://github.com/zd87pl/loci-db.git
cd loci-db
pip install -e ".[dev]"
pytest tests/ -v
# Linting & formatting (must pass in CI)
ruff check loci/ tests/
ruff format --check loci/ tests/
mypy loci/
```
## Roadmap
See [ROADMAP.md](ROADMAP.md) for the v0.1 → v1.0 plan.
## Citation
```bibtex
@misc{loci2026,
title={LOCI: A 4D Spatiotemporal Vector Database for AI World Models},
author={Dyras, Zygmunt},
year={2026},
url={https://github.com/zd87pl/loci-db}
}
```
## License
Apache 2.0