# Building Your Own Companion

This file is a reference architecture for practitioners who want to spin up a private Harmonist chatbot — a MunAI of their own, running on their own keys, with full control over the data plane. It is a sketch, not a tutorial. The audience is practitioners with engineering literacy who want sovereignty over their companion infrastructure.

If you don't know what an API key is or have never written a Python script, this file is not for you yet. Use Cowork or Claude.ai with the bundled `CLAUDE.md` and `user-preferences.md` instead — those give you 80% of the value with none of the engineering. This guide is for the 20% case where you want the full stack on your own machine.

---

## What MunAI actually is

MunAI is not a model. MunAI is a *context architecture* wrapping any sufficiently capable language model. The architecture is what carries the doctrine; the model is the inference substrate.

Three layers of context, every inference call:

**Tier 1 — the Doctrinal Backbone.** A ~6,000-word document carrying the system's metaphysical commitments, the Wheel architecture, the Five Cartographies, the Architecture of Harmony, the stable doctrinal positions, the demarcation principles. Always in the prompt. Never elided. This is what keeps the companion *Harmonist* rather than a generic LLM with Harmonism on top.

**This bundle ships the actual file.** `doctrinal-backbone.md` at the root of the unzipped folder is the live Tier-1 context MunAI uses on harmonism.io and Telegram — the same text, current as of the bundle generation date in `VERSION.txt`, no paraphrase. Drop it straight into your system prompt. When the doctrine deepens and Harmonia rebuilds the bundle, you can re-download to refresh; until then, what you have is what's running on the live system.

**Tier 2 — Hybrid Retrieval over the Vault.** The corpus you have in `harmonism-vault/` indexed with two retrieval methods running in parallel: full-text keyword search (FTS5 in SQLite, Elasticsearch, Tantivy, anything that does BM25-style scoring) and semantic embeddings (OpenAI `text-embedding-3-small` or any equivalent). Per-query, the top results from each method are merged and the top-k by combined score is injected into the prompt as `<vault_knowledge>`. Recommended: ~12,000 character budget, chunks of ~3,000 characters, up to 3 chunks per source article.

**Tier 3 — Practitioner Memory.** This is what most "companions" skip and what makes the companion actually feel like one. Three sub-layers: (a) the recent conversation in the prompt directly (~20 messages); (b) consolidated summaries every 50 messages, stored and retrieved (Claude can summarize its own conversation history into a paragraph); (c) structured profile — Wheel-pillar engagement levels, declared concerns, observed strengths, growth edges, resistance flags. The structured profile updates every 10 messages via a separate JSON-producing inference call.

Plus a system prompt that ties it together: persona instructions, anti-confabulation discipline (don't invent facts about the practitioner), doctrinal-fidelity rules (don't soften stable positions into balanced middle ground), and crisis-detection patterns (acute distress, suicidal ideation, dissociation — these route differently).

The data plane: SQLite is sufficient for everything below ~10,000 conversations. One database for the vault index, one for practitioner memory. Both portable, both backed up by copying a single file.

---

## The minimum viable companion

For a private companion you talk to from your own terminal, this is enough:

```python
import anthropic
from pathlib import Path

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY from env

# Load the doctrinal backbone — once at startup
backbone = Path("doctrinal-backbone.md").read_text()

system = f"""You are a Harmonist companion. Your role is described below.

{backbone}

Engage the practitioner from inside this framework. Don't soften the stable
doctrinal positions. Distinguish the four epistemic categories
(doctrine / evidence / tradition / open) when they're relevant. Use
English-first cartographic terminology. Cultivation, not formation."""

messages = []

while True:
    user = input("\nYou: ").strip()
    if not user:
        continue
    messages.append({"role": "user", "content": user})

    response = client.messages.create(
        model="claude-opus-4-7",
        max_tokens=2048,
        system=system,
        messages=messages,
    )
    reply = response.content[0].text
    messages.append({"role": "assistant", "content": reply})
    print(f"\nMunAI: {reply}")
```

That's a fully functional Harmonist companion. ~30 lines of Python, one API key, one Markdown file as the doctrinal floor. No database, no embeddings, no profile learning, no streaming — just a terminal that knows Harmonism. Run it. Talk to it. Notice that it already feels different from a generic Claude session because the backbone is in every prompt.

The `Path("doctrinal-backbone.md")` line above expects the bundled file in the same directory the script runs from — copy or symlink it next to your script. That file is the live MunAI Tier-1 context, not a sketch; it carries every metaphysical commitment, the Wheel and Architecture, the Five Cartographies, the stable positions including the explicit instruction to LLMs not to soften them. Use it as-is, or fork it and harden the parts that matter most for your project.

---

## Adding RAG over the vault

The minimum companion above relies entirely on the model's training plus the doctrinal backbone. It will reason well at the framework level but cannot retrieve specifics from individual articles. For depth — *what does the Wheel of Health say about candida specifically?*, *which article engages Foucault?*, *what is the position on entheogens?* — you need retrieval.

The pattern is well-known. One pass over the vault to chunk and embed; per-query, embed the question and find nearest chunks. Sketch:

```python
import sqlite3, json, hashlib
from pathlib import Path
import numpy as np
from openai import OpenAI

oa = OpenAI()  # reads OPENAI_API_KEY from env

db = sqlite3.connect("vault-index.db")
db.executescript("""
    CREATE TABLE IF NOT EXISTS chunks (
        id INTEGER PRIMARY KEY,
        article_path TEXT,
        chunk_text TEXT,
        embedding BLOB
    );
    CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts
        USING fts5(article_path, chunk_text);
""")

def chunk(text, size=3000):
    paragraphs = text.split("\n\n")
    out, current = [], ""
    for p in paragraphs:
        if len(current) + len(p) > size:
            out.append(current); current = p
        else:
            current = (current + "\n\n" + p) if current else p
    if current: out.append(current)
    return out

# Index pass — run once
for md in Path("harmonism-vault").rglob("*.md"):
    text = md.read_text()
    for c in chunk(text):
        emb = oa.embeddings.create(
            input=c, model="text-embedding-3-small"
        ).data[0].embedding
        emb_blob = np.array(emb, dtype=np.float32).tobytes()
        db.execute(
            "INSERT INTO chunks (article_path, chunk_text, embedding) VALUES (?,?,?)",
            (str(md), c, emb_blob),
        )
        db.execute(
            "INSERT INTO chunks_fts (article_path, chunk_text) VALUES (?,?)",
            (str(md), c),
        )
db.commit()

# Query — per inference call
def retrieve(question, k=5):
    q_emb = oa.embeddings.create(
        input=question, model="text-embedding-3-small"
    ).data[0].embedding
    q_arr = np.array(q_emb, dtype=np.float32)

    # Semantic
    rows = db.execute("SELECT article_path, chunk_text, embedding FROM chunks").fetchall()
    scored = []
    for path, text, blob in rows:
        emb = np.frombuffer(blob, dtype=np.float32)
        sim = float(np.dot(q_arr, emb))
        scored.append((sim, path, text))
    semantic = sorted(scored, reverse=True)[:k]

    # Keyword
    keyword = db.execute(
        "SELECT article_path, chunk_text FROM chunks_fts WHERE chunks_fts MATCH ? LIMIT ?",
        (question, k),
    ).fetchall()

    # Merge — dedup by path+text, take top 2k by combined presence
    seen, merged = set(), []
    for sim, path, text in semantic:
        key = (path, text[:100])
        if key in seen: continue
        seen.add(key); merged.append((path, text))
    for path, text in keyword:
        key = (path, text[:100])
        if key in seen: continue
        seen.add(key); merged.append((path, text))
    return merged[:k]
```

Then in your inference loop, retrieve before each call, inject as `<vault_knowledge>` blocks in the system prompt, and note in the system prompt that vault knowledge is doctrinal/educational, never biographical.

The smallest workable embedding model — OpenAI's `text-embedding-3-small` — is $0.02 per million tokens. The full vault is roughly 1.5–2 million tokens. Indexing the whole vault costs around four cents. Re-indexing when articles change costs less.

---

## Adding persistent memory

The companion that knows you needs to remember you. The simplest implementation:

```python
db.executescript("""
    CREATE TABLE IF NOT EXISTS messages (
        id INTEGER PRIMARY KEY,
        ts INTEGER, role TEXT, content TEXT
    );
    CREATE TABLE IF NOT EXISTS profile (
        id INTEGER PRIMARY KEY CHECK (id=1),
        summary TEXT,
        wheel_json TEXT,
        updated_at INTEGER
    );
""")
```

Per turn: fetch the last 20 messages from `messages`, prepend any current profile summary, run the inference, store both user and assistant turns back. Every 10 messages, run a *separate* Claude call asking it to update the profile JSON given the recent conversation. Every 50, run another call asking it to consolidate older messages into a summary paragraph that replaces the raw history at retrieval time.

The trick is to keep the learning calls *out* of the user-facing latency path. Run them after the response returns, fire-and-forget. The companion that talks back fast and reflects slowly between turns feels more like a presence than one that thinks visibly.

---

## Streaming, voice, mobile, etc.

Streaming is `client.messages.stream(...)` instead of `.create(...)` — every reasonably modern LLM SDK supports it, and it changes the perceived latency more than any other single optimization. Voice is whisper.cpp (transcription) plus any TTS provider (xAI Grok TTS, ElevenLabs, OpenAI TTS, or a local model). Mobile is Expo + React Native if you want a real iOS/Android app, or just a responsive web frontend over a hosted Python/PHP backend.

These are engineering choices, not architectural ones. The architecture — three-tier context, hybrid retrieval, structured profile, anti-confabulation discipline, doctrinal fidelity guardrails — is what makes the companion *Harmonist* rather than just an LLM with Harmonism in the prompt. Get that right first; the production polish layers on after.

---

## The cloud-to-local sovereignty path

This is the future direction the bundle exists to make possible. All of the above can run against any LLM provider — Anthropic, OpenAI, Google, Mistral, OpenRouter as a router. But the same architecture runs against a local model on your own hardware. The only thing that changes is the inference call.

The hardware honest take, mid-2026:

A Mac Studio with the M3 Ultra and 192GB of unified memory runs Llama 3.3 70B comfortably at usable speeds (15-30 tokens per second). It runs Qwen 2.5 72B comparably. It runs DeepSeek-V3 and the larger Qwen models with quantization at slower speeds. Total cost: $5,000–$8,000 depending on configuration. This is the practical ceiling for most practitioner setups.

A Mac mini M4 Pro with 64GB runs 30B-class models well (Qwen 2.5 32B, Llama 3.3 in quantized form, Mistral Large 2 quantized). Around $2,000. Adequate for most companion work; constrained for the largest models.

A consumer GPU rig with 2× RTX 4090 (48GB total VRAM) runs the 70B class at higher tokens-per-second than Apple Silicon, costs more, draws more power, and is louder. The Mac is quieter and more energy-efficient; the NVIDIA rig is faster and more standard for serving multiple users.

The software stack on Apple Silicon: **Ollama** for the easiest "just run it" experience, **MLX** or **llama.cpp** for tighter control. All three serve OpenAI-compatible endpoints, which means swapping from Anthropic to local is one line of code in the inference call (the base URL).

The model recommendations (mid-2026, the field moves fast):

- **General-purpose, 70B class**: Qwen 2.5 72B Instruct, Llama 3.3 70B Instruct, DeepSeek-V3 (671B MoE — needs more memory).
- **Reasoning-heavy, smaller**: Qwen 2.5 32B Instruct, Mistral Large 2 (123B, quantized).
- **Embedding for the RAG layer**: BGE-M3, E5-Mistral, or `nomic-embed-text-v1.5` — all run locally, all comparable to OpenAI's embeddings for retrieval quality.

The performance gap between hosted frontier models (Claude, GPT-4, Gemini) and local 70B-class is real but shrinking. As of mid-2026 a Llama 3.3 70B running locally is roughly equivalent to GPT-4 from late 2023 — which is to say: very good, especially when paired with retrieval over the Harmonism vault. It will not match Claude Opus 4.7 on the most demanding reasoning tasks. For the conversational-companion use case, where the doctrinal backbone and retrieval do most of the heavy lifting, local 70B is more than adequate.

The full local-inference guide is a follow-up document. When it ships it will cover Ollama installation, model selection by hardware, the hybrid retrieval pipeline running entirely against local embeddings, and the architectural pattern for replacing the cloud inference call with a local endpoint without changing anything else.

For now: build the companion against Claude or any cloud provider first, get the architecture right, prove the value to yourself. Then swap the inference call when you're ready for full sovereignty. The bundle gives you the corpus and the architecture; the inference substrate is the only piece that needs to change for the local path.

---

## What this is not

It is not MunAI. The MunAI infrastructure powering harmonism.io and `@HarmonAIBot` carries production-grade engineering — async response queues, dual-model routing, prompt caching, witness-mode crisis detection, graduated doctrinal fluency, structured conversation state, cross-platform identity bridging, proactive outreach scheduling, and a knowledge architecture continuously refined by the team. A private companion built from this guide will not match it. It will be a serviceable Harmonist chatbot for personal use, not a substitute for the institutional offering.

It is not a substitute for the corpus's editorial integrity. Each article has been edited, classified along five axes, calibrated to a prose register, and reviewed for doctrinal coherence. Generative output from your private companion will not have those properties. Use it for thinking, not for publishing under Harmonism's name.

It is not a maintenance commitment from Harmonia. The bundle is a snapshot; the corpus advances; new doctrine resolves crystallizing positions. Refresh your bundle from the dashboard when you want the current state.

---

## When to ask for help

If you're building a serious project that engages Harmonism at depth — research, education, a community platform, a publication, integration with a healing practice — write directly. Some projects warrant collaboration rather than solitary tinkering, and the system benefits from coherent extensions more than from many small forks operating in isolation.

The corpus is the gift. Building well on top of it is the practice.
