Working, tested implementation of the design doc's pipeline. Pure Python,
one dependency (jellyfish), single SQLite file. This is the engine you
port to Kotlin for the Android/Wear app — every module maps 1:1.
A typical RAG agent chunks notes, embeds them, retrieves top-k, and lets the model answer. That works for documents. It fails for a personal relationship graph — and those failures are exactly what relationOs is built to prevent.
| Where a RAG agent fails | What relationOs does instead |
|---|---|
| Soft-matches names → silent wrong merges ("Arjoon" ≈ Arjun at 0.65) | Gray-zone ER: ask before merge; never invent a person for an ambiguous mention |
Treats Ma Clg Keerthana as one opaque string |
Token-level evidence: labels vs names measured from your corpus |
| Model "walks" the graph and invents paths | Deterministic BFS (paths / edges_within); LLM only phrases the result |
| Chunks have no typed identity or edge direction | Typed entities + relations; "A parent B" is structure, not prose |
| No reliable "why do you think that?" | Append-only facts with source_note_id; corrections supersede, never rewrite |
| Unbounded context → essay answers | Token-lean slice (≤3 people) + briefing hard-cap of 3 |
| Agent owns the loop; retrieval is opaque | Hybrid retrieve (FTS + cosine) feeds a grounded synthesizer that must not invent |
relationOs still uses retrieval and an LLM — for extraction and phrasing. The engine owns identity, provenance, and graph truth. The model never does.
pip install jellyfish pyyaml fastapi uvicorn pytest httpx
cp config.example.yaml config.yaml # all settings + API keys (no env vars)
python -m relation_os.server # HTTP API on :8321
python -m relation_os.telegram_bot # needs telegram.bot_token; empty allowed_user_ids = allow all
# bot: each Telegram sender gets an isolated graph (X-User-Id);
# send text OR voice notes (whisper-large-v3-turbo via Groq) to save;
# send a photo captioned with the person's name to attach it;
# "/photo Arjun" or "show me picture of Arjun" to get it back;
# end any message with "?" and the LLM answers by reasoning over the graph:
# "What gift should I get Arjun?" -> grounded in pens + surgery + daughter
python -m pytest tests/ # must stay green
python -m relation_os.cli note "person: Arjun; alias: AJ; int: fountain pens"
python -m relation_os.cli note "person: Arjoon; loop: ask about surgery @2026-08-15"
# -> CONFIRM? 'Arjoon' == person#1 (P=0.65) gray zone, never silent-merges
python -m relation_os.cli confirm 2 1
python -m relation_os.cli brief Arjun # loop-weighted top-3 briefing
python -m relation_os.cli ask "pens" # BM25 + vector, RRF-fused
python -m relation_os.cli slice AJ # ~15-token/line LLM context| File | Doc § | What's real |
|---|---|---|
db.py |
3 | Full schema: append-only facts, provenance, FTS5, vec blobs, er_log |
er.py |
5 | Blocking (phonetic + JW≥0.8), logistic score, 0.90/0.55 thresholds, decision logging for weight calibration |
engine.py |
6, 7 | RRF (k=60) over FTS5 BM25 + brute-force cosine; briefing score with 45-day half-life, loop×3, date proximity; gray-zone merge |
extract.py |
4 | Extractor interface + deterministic grammar mock |
embed.py |
2 | Embedding interface + hashing embedder |
cli.py |
11 | Product loop + token-lean slice serializer |
llm_extract.py |
4 | Real sentence→graph: Anthropic, Groq, or Gemini adapter, constrained-JSON prompt (configure the selected API key in config.yaml; on Android swap _call for MediaPipe/Gemma) |
extract.RuleExtractor— deterministic dictation grammar so the pipeline runs offline and tests are reproducible. Production: one constrained-JSON LLM call implementing the sameExtractiondataclass.embed.embed()— feature-hashing embedder (lexical overlap only, no semantics). Production: on-device sentence model, 256d int8. Nothing downstream changes;EMBED_DIMis the only constant.
Everything else — schema, ER math, thresholds, RRF, ranking, merge, loop resolution, slice format — is the production algorithm, not a mock.
db.py→ Room/SQLDelight + FTS5 (bundled SQLite), vec blobs as ByteArrayer.py→ same math; JW + Metaphone are ~80 lines of Kotlin or Apache Commons Textengine.py→ plain Kotlin class, WorkManager job for async ingestextract→ MediaPipe LLM Inference API (Gemma), JSON-schema constrainedembed→ MediaPipe text embedder / LiteRT model
- Gray-zone (0.55–0.90) never auto-merges;
merge_entityis user-triggered. - Resolved loops drop out of briefings but stay in the graph (provenance).
- Recency term is exactly 0.5 at 45 days (
test_recency_decay). - Briefings hard-cap at 3.