A local-first context graph engine. One Rust binary that turns a stream of text into a queryable graph of entities, relationships, and episodes — combining property-graph storage, HNSW vector search, and full-text search in a single embedded service. No database server, no separate vector store, no search cluster: everything runs in one process, on your machine, against files in your workspace.
Originally inspired by the knowledge-graph ideas in graphiti, then deliberately narrowed: instead of a general framework over pluggable backends, liminis-context-graph is a purpose-built engine with one storage layer, one wire protocol, and a local-first design from top to bottom.
- One embedded engine. LadybugDB (the community continuation of KuzuDB) provides the property graph, HNSW vector indices, and full-text search in a single embedded database — no server process, no network hop, data in ordinary files under your workspace.
- The write-ahead log is the source of truth — and it's just JSON. Every mutation is appended to plain JSONL files under
.lcg/wal/before it touches the database. The database is a derived index — delete it andknowledge_rebuild_from_walreconstructs the entire graph from the log. - One database, many graphs.
.lcg/wal/is a WAL root: eachgroup_idowns its own stream in.lcg/wal/<group_id>/, independently replayable and independently discardable. One process can hold several graphs at once without them bleeding into each other, and because a stream is just a directory of JSONL files it can be versioned, shipped, and replayed somewhere else. - Models stay out of process. Embedding and LLM inference are reached through narrow adapters over the OpenAI-compatible
/v1/embeddingsand/v1/chat/completionsshapes. Embedding runs fully local out of the box on macOS; extraction can run fully local too, or against the hosted Anthropic API.
The result is a context graph you can treat like the rest of your local tooling: a single process, a directory of files, versionable with git, rebuildable from its own log.
┌─────────────────────────────────────────────────┐
text chunks │ liminis-context-graph (one process) │
──────────────────► │ │
JSON-RPC 2.0 │ extraction LLM ──► entities + relations │
over Unix socket │ (out-of-process) dedup + resolution │
│ │ │
search queries │ 1. append ┌────▼───────┐ .lcg/wal/ │
──────────────────► │ ────────► │ WAL (JSONL)│ <group_id>/ │
hybrid results │ └────┬───────┘ one stream │
◄────────────────── │ 2. apply │ per graph, │
│ │ source of │
│ │ truth │
│ ────────► ┌────▼───────┐ │
│ │ LadybugDB │ .lcg/db/ │
│ embedder sidecar │ graph+HNSW │ derived │
│ (out-of-process) │ +FTS │ index │
└─────────────────────┴────────────┴──────────────┘
Ingestion: knowledge_process_chunk sends a chunk of text through the extraction LLM, which returns typed entities and relationships (optionally constrained by your ontology). New facts are deduplicated against the existing graph, appended to the WAL, then written to the database with embeddings from the sidecar. Every chunk becomes a time-stamped episode linked to the facts it produced. Recommended maximum chunk_text size is 8,000 characters (default, overridable via LCG_CHUNK_TEXT_ADVISORY_MAX_CHARS — see Configuration): extraction quality degrades well before any context-window limit is reached, and splitting oversized input into multiple knowledge_process_chunk calls is the caller's responsibility. The call still succeeds above the threshold — nothing is truncated, split, or rejected — but the result gains a warning field naming the actual size and the recommended maximum.
Search is hybrid by default: knowledge_find_entities combines full-text search with two vector similarity signals (an entity's name and its summary, so a query that paraphrases an entity's summary — sharing no vocabulary with it — still finds it) via Reciprocal Rank Fusion; knowledge_find_relationships combines full-text and vector similarity; knowledge_search_passages does semantic passage retrieval; knowledge_get_entity_neighbors and knowledge_query_cypher traverse the graph directly. Entities created before this summary-vector capability existed become semantically retrievable by summary via the knowledge_backfill_summary_embeddings admin tool.
Graphs are separated by group_id, and each one owns its WAL stream. A group_id is a real isolation boundary, not a filter applied after the fact: entity resolution, dedup, merge, and purge are all scoped to it, so one graph's ingest cannot rewrite or delete another's data. Each group's mutations land in .lcg/wal/<group_id>/, which makes a single graph independently replayable (knowledge_rebuild_from_wal), independently disposable (knowledge_delete_by_group), and independently shippable — a stream is a directory of JSONL files, so it can be committed to git, distributed, and hydrated into someone else's database.
That is what makes replication and layering practical. A consumer can hydrate several upstream streams into one local database, each keeping its own group_id, and still query any one of them in isolation or all of them together. A stream carries a generation identity (wal.generation) so a consumer can tell a forward advance from a reset and never mistakes a rebuilt upstream for an extension of the one it already replayed. Because entities in different groups stay distinct at the graph layer, references between graphs are expressed as resolvable pointers (knowledge_add_cross_group_edge, knowledge_rebind_pointers) rather than raw edges — so a layer graph can carry its own group_id and connect entities across two source graphs without either source having to know about it, and without the link dangling when a source is re-ingested.
Two transport surfaces. By default the engine serves the Unix-socket JSON-RPC protocol shown above. It can equally run as a native Model Context Protocol server over stdin/stdout (--mcp-stdio), pointing any MCP client straight at the graph with no app or custom client in between. See the IPC & MCP Reference.
No Rust toolchain required:
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/verveguy/liminis-context-graph/releases/latest/download/lcg-service-installer.sh | shPrebuilt binaries are published for macOS (Apple Silicon), Linux x86_64, and Linux ARM64 on every tagged release. If macOS blocks the binary, clear the quarantine attribute: xattr -d com.apple.quarantine ~/.cargo/bin/liminis-context-graph.
An embedder is required at runtime — see Configuration: Embedder sidecar.
# start your embedding service first — see Configuration: Embedder sidecar
cd your-workspace/ # the directory whose content you're indexing
liminis-context-graph # creates .lcg/, binds .lcg/service.sockThe service speaks newline-delimited JSON-RPC 2.0 over the socket — from any language:
import socket, json
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(".lcg/service.sock")
f = s.makefile("r", encoding="utf-8")
def call(method, params, id=1):
s.sendall((json.dumps({"jsonrpc": "2.0", "id": id, "method": method, "params": params}) + "\n").encode())
return json.loads(f.readline())["result"]
# ingest a chunk of text
call("knowledge_process_chunk", {
"chunk_text": "Ada Lovelace wrote the first program for Babbage's Analytical Engine.",
"chunk_id": "notes-0001",
"source_file": "notes.md",
})
# hybrid (full-text + vector) entity search
print(call("knowledge_find_entities", {"query": "early computing pioneers", "num_results": 5}, id=2))
# graph + WAL health at a glance
print(call("knowledge_status", {}, id=3))Or run it as a native MCP server instead — add to your client's MCP config:
{
"mcpServers": {
"liminis-context-graph": {
"command": "liminis-context-graph",
"args": ["--mcp-stdio", "--scope=read,write"],
"cwd": "/path/to/your-workspace"
}
}
}Requires Rust/Cargo, a C++20 compiler, and OpenSSL 3. The first build downloads a prebuilt lbug bundle (LadybugDB bindings), so the graph engine itself is never compiled — no cmake build step and no C++ dependency tree. lbug's build.rs does still compile its own small cxx FFI bridge locally at -std=c++2a, which is why a C++20 compiler is needed (GCC 13+ / a recent Clang; Ubuntu 22.04's GCC 11 is too old, as it lacks <format>). The bundle statically ships its other third-party dependencies, but since lbug 0.18.0 it links OpenSSL externally, so you also need openssl@3 (macOS: brew install openssl@3; Debian/Ubuntu: apt install libssl-dev).
This applies to building from source only. Released binaries link OpenSSL statically and require nothing installed — see ADR-0398:
cargo build --release # build both crates
cargo test -p lcg-core # integration tests (LadybugDB round-trip)
cargo run --example basic_ingest -p lcg-core # example: ingest 3 docs, search, print
cargo run -p lcg-service # run the service binarySee Getting Started for downstream-app bundling and pinned-release tarball URLs.
In scope: a single-user, local-first context graph engine, shipped as a library crate (lcg-core) and an IPC binary (lcg-service) that are peers — embed it in a Rust application, or drive it from any language over the socket.
Multi-graph, not multi-tenant. One process can hold many graphs, each with its own group_id and its own WAL stream, and a graph can be replicated into another database by shipping that stream. That is a data-organisation capability for one user's own workspaces and subscriptions — it is not tenancy. There is no authentication, no authorisation, and no per-tenant resource isolation: anything that can reach the socket can reach every group in the database. Treat the process boundary as the trust boundary.
Out of scope, by design:
- Storage engines other than LadybugDB — the single-engine bet is what keeps the service embedded, fast, and simple to operate.
- In-process ML runtimes (
tch,candle,onnxruntime) — embeddings and extraction stay behind out-of-process adapters. - Hosted or multi-tenant deployment — this is local-first infrastructure: one process, on your machine, serving one user. Groups separate that user's graphs from each other; they do not separate users from each other, and are not a security boundary.
Full reference documentation is published at v3rv.com/liminis-context-graph:
- Getting Started — install, run, build from source, bundle in downstream apps.
- Configuration — every environment variable and CLI flag.
- IPC & MCP Reference — the JSON-RPC and Model Context Protocol method surface.
- Telemetry — structured JSONL events emitted on stderr.
- Ontology — the optional entity/relation type vocabulary.
- Operations — WAL administration, degraded mode, and self-healing recovery.
- Testing & Evaluation — LLM cassettes and the extraction-quality eval harness.
- ADR Index — architecture decision records (historical, not current-state, documentation).
The site documents the version stated on its home page and may lag main between releases; this README's quickstart always works against main.
crates/core/ # lcg-core: library crate — all DB interaction
crates/core/benches/ # performance benchmarks (criterion)
crates/core/examples/ # standalone consumers demonstrating the library API
crates/service/ # lcg-service: binary crate — IPC service (builds `liminis-context-graph`)
crates/eval/ # lcg-eval: binary crate — extraction-quality eval harness
native/local-inference/ # Swift CoreML embedding/LLM sidecar for macOS
docs/ # documentation site source (published at v3rv.com/liminis-context-graph)
docs/adr/ # architecture decision records (index at docs/adr/index.md)
specs/ # feature specifications
| Crate | Version | Role |
|---|---|---|
lbug |
=0.19.1 |
LadybugDB Rust bindings (pinned) |
thiserror |
2 |
Error type generation |
No ML-runtime dependencies (tch, candle, onnxruntime) are permitted — embeddings are produced out-of-process.
See docs/adr/ for recorded architecture decisions (index). The project constitution lives at .specify/memory/constitution.md.
Contributions are welcome. See CONTRIBUTING.md for how to file issues, submit pull requests, and the required pre-commit checks. No CLA or DCO sign-off is required — contributions are accepted under the project's MIT license by inbound=outbound convention.
To report a security vulnerability, please use GitHub's private vulnerability reporting rather than filing a public issue. See SECURITY.md for supported versions, response time, and disclosure policy.