Skip to content

About

relationOs — voice-first personal CRM / relationship graph engine

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

13 Commits

Folders and files

Repository files navigation

relationOs — Core Engine MVP

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.

Why not a RAG agent?

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.

Run

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

Modules → design doc

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)

The two intentional stubs

  1. extract.RuleExtractor — deterministic dictation grammar so the pipeline runs offline and tests are reproducible. Production: one constrained-JSON LLM call implementing the same Extraction dataclass.
  2. embed.embed() — feature-hashing embedder (lexical overlap only, no semantics). Production: on-device sentence model, 256d int8. Nothing downstream changes; EMBED_DIM is the only constant.

Everything else — schema, ER math, thresholds, RRF, ranking, merge, loop resolution, slice format — is the production algorithm, not a mock.

Kotlin port map

  • db.py → Room/SQLDelight + FTS5 (bundled SQLite), vec blobs as ByteArray
  • er.py → same math; JW + Metaphone are ~80 lines of Kotlin or Apache Commons Text
  • engine.py → plain Kotlin class, WorkManager job for async ingest
  • extract → MediaPipe LLM Inference API (Gemma), JSON-schema constrained
  • embed → MediaPipe text embedder / LiteRT model

Non-obvious behaviors the tests pin down

  • Gray-zone (0.55–0.90) never auto-merges; merge_entity is 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.

About

relationOs — voice-first personal CRM / relationship graph engine

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages