The full adaptive Hermes harness, backed by Grok 4.3, composing OiMy-quality replies natively — grounded every turn in OiMy's tested knowledge assets, not a persona sketch. Architected so a later swap to a finetuned Gemma 4 is a config change, not a rewrite.
Two hard facts make static injection impossible — and point straight at the right design instead.
hermes-agent caps SOUL.md at 20,000 characters (CONTEXT_FILE_MAX_CHARS = 20_000, prompt_builder.py:819). The coaching-anchors file alone is 135 KB. And OiMy already solved "too much knowledge for one context" with per-turn retrieval and assembly — tuned, tested, single-sourced. Heavy Hermes should consume that machinery, not duplicate it.
soul.d/: voice doctrine, anti-sycophancy, coaching foundation, planning, safety, and a grounding protocol — each tagged absorb: finetune or absorb: never.POST /context ("grounding pack") that exposes exactly what _call_companion assembles today — entity context, profile, top-K coaching anchors, constraint ledger, Honcho insight — as labeled JSON blocks, per user, per message.pre_llm_call shell hook in Hermes that fetches the pack every turn and injects it deterministically. This kills the observed failure mode at the root: grounding no longer depends on the model deciding to call a tool.POST /context/writeback so the engine's learning loops keep learning from turns Hermes composed — otherwise the deep profile goes stale and heavy Hermes slowly lobotomizes the product.build_soul.py --target gemma4-ft drops the absorb: finetune modules; everything tagged absorb: never (live family data, ledger, safety) keeps flowing unchanged — and matters more post-swap, because the finetune is trained with those blocks present.Heavy Hermes is the config Bharath's original SOUL.md vision wanted — a frontier model running the full adaptive harness — but fed the tested OiMy content instead of improvising from a persona sketch.
| OiMy-as-tool (old default) | Light Hermes (built tonight) | Heavy Hermes (this doc) | |
|---|---|---|---|
| Hermes persona | Full SOUL.md (12 KB, "Oi") | Gutted to thin router | Full harness persona, restored + modularized |
| Hermes model | kimi-k2.6 | kimi-k2.6 | x-ai/grok-4.3 |
| Who composes the reply | OiMy engine — when Hermes calls it | OiMy engine, always | Hermes / Grok itself |
| Grounding | Engine-internal | Engine-internal | Grounding pack pulled per turn |
| Failure mode | Hermes improvises instead of calling the tool | Harness capability wasted | Voice/asset drift — mitigated by single-sourcing (§5) |
| Engine's role | Full product | Full product | Knowledge + memory service |
| Learning loop | Sees every turn | Sees every turn | Sees every turn via writeback |
It is also, not incidentally, a teacher-model data factory: heavy-Hermes transcripts (Grok 4.3 + full grounding) are close to ideal supervised examples for the Gemma 4 finetune — the same teacher/student doctrine applied one level up.
Every asset is one of three kinds. The classification decides whether a finetune can eventually absorb it — or whether it must be injected live, forever.
"Absorbable" is defined against a specific finetune — the gold-standard multi-day dataset (17 families, 42-day arcs, ~874 trainable rows). Two properties drive the whole categorization: training rows are production-context-shaped (the finetune is trained to consume runtime context blocks, not operate without them), and the 17 families are synthetic — no real family's data is, or ever will be, in the weights. Every real roster, profile, ledger and history is category (B) forever.
prompts/deepseek-coaching-anchors-v1.txt/context.prompts/gemma4_12b_companion_combined_v2.txtSOUL.md.bak.pre-routing-override-20260710archetypes/planner.pyconstraints_satisfied, constraints_unknown, and risks_or_checks; live local facts (hours, prices, availability) always flagged "verify before relying." (B) The live constraint ledger: h30.update_constraint_ledger() extracts constraints from the message and injects them as a hard-rule block — per-user, per-session, always runtime.archetypes/inference.py + prompts/deep-inference-v6.txtclaude-opus-4.8). Its outputs are pure (B) — exactly what can never be finetuned. _build_profile_context() assembles, capped 2,500 chars: [Facts] Tier-1 entity facts, [Psych] DISC type / Hughes need / attachment style (the keys the anchors' delivery variants switch on), [Inferences] Tier-3 top-5 with confidence, [H3.0] engagement + life-domain, [Failed Approaches]. Plus entity roster (never-invent-names rule), Honcho insight, current topic, open threads.archetypes/h30.pyBCT_HINTS delivery-style paragraphs (conformist / individualist / synthesist) are (A); everything per-user is (B).archetypes/bct_engine.py · intent_router.py + manifests/*.yaml[H3.0] gating hint — do not port it separately in v1. Routing scaffolds (203 KB) mostly aren't needed: Grok 4.3 with the grounding pack doesn't need a pre-classified skill id. Exception: /context still runs the router internally (cheap GPT-5.4-mini) so anchor retrieval gets skill-boost and Tier-3 inferences get filtered. Hermes never sees it._call_companion (api.py:4387)_call_companion wraps every companion call with: roster unknown-name check, ledger-exclusion on deliverables, opener-variety governor, clinical-language literal + embedding checks, unsolicited-advice check on venting turns, content-recycling check — all folded into ≤1 bounded regeneration. These exist because a 12B model ignores prompt rules. Grok 4.3 needs them less, but "less" is unmeasured.archetypes/vector_memory.pytext-embedding-3-small but OLLAMA_URL points at OpenRouter, which 401s on embeddings. Meanwhile anchor_retrieval.py already runs local fastembed BGE-small (ONNX, ~5 ms). Remediation (independent of this project): port vector_memory to the same local pattern. Until then, semantic recall of past conversations is absent from every configuration.Four constraints in hermes-agent 0.15.2 shape the design — and one of them is the escape hatch that makes it work.
prompt_builder.py:819,1287)..hermes.md > AGENTS.md > CLAUDE.md). Usable as a second static slot if SOUL overflows.pre_llm_call hook can return {"context": "..."} and that context is injected into the call. This is deterministic — it does not depend on the model's tool choice.chat_id → user_id, per-sender JWT — same rules as today's SKILL.md.oimy-heavy · model x-ai/grok-4.3include: [family, profile, anchors, ledger, honcho, topic], max_bytes: 16000 → returns labeled blocks; hook emits {"context": "<blocks>"}store_message · h30 ledger/openings · Honcho appendThe engine's /chat remains untouched — light Hermes and the iOS/WhatsApp paths keep working. Heavy Hermes only adds two read/write endpoints beside it.
/context endpoint (new, in api.py)A thin HTTP wrapper over code that already exists — assembly order and caps copied from _call_companion's call sites.
// Authorization: Bearer <per-sender JWT> (body.user_id == JWT-subject)
→ 200 {
"blocks": {
"family": "[FAMILY — people in this parent's household...]", // ≤600
"profile": "[User]...[Facts]...[Psych]...[Inferences]...[H3.0]...", // ≤2500
"anchors": "[COACHING ANCHORS — deliver as your own insight] ###...", // ≤12288, 0–3
"ledger": "[CONSTRAINT LEDGER — HARD RULES]\n- no pork\n- budget $60/wk",
"honcho": "[HONCHO] ...",
"topic": "[CURRENT TOPIC] ..."
},
"anchors_delivered": ["becky-kennedy-good-inside"], // deliver-once bookkeeping
"meta": { "skill_hint": "parenting-coach", "router_confidence": 0.86 }
}
skill_hint → anchor skill-boost + Tier-3 filtering. Hermes never sees it.h30.update_constraint_ledger() before reading the ledger, so a constraint stated this turn binds this turn.soul.d/ + manifest)Single-sourced in the engine repo, not in ~/.hermes. build_soul.py compiles the manifest into a per-target SOUL.md and enforces the 20 K cap; the profile file is a build artifact.
00-identity.md # Oi persona core + inner life ~2.0K
05-safety.md # crisis gate, medical boundaries ~1.5K absorb: never
08-grounding.md # how to use [FAMILY]/[PROFILE]/[ANCHORS] ~1.5K absorb: never
10-voice.md # distilled companion doctrine (combined_v2) ~8.0K
15-anti-sycophancy.md # hard rules block, verbatim ~1.2K
20-coaching-foundation.md # DISC/attachment delivery switching ~1.5K
30-planning.md # constraints stated>inferred>default ~1.5K
40-telegram.md # formatting, reactions, identity ~1.2K absorb: never
50-harness.md # relationship modes 1-4, proactive, tools ~1.8K absorb: never
── total ≈ 18.2K < 20K ✅
Where a module distills a live engine prompt (10-voice from combined_v2), the header records source file + mtime, and build_soul.py --check warns when the source is newer than the distillation — a drift alarm (§6.2).
state.db, FTS-indexed, context_window: 20, overflow summarization) gives real multi-turn continuity — better than the engine's own history handling. Keep it./context, fed via writeback. Hermes' own memories/ plugin stays disabled for family facts — two competing memories is how you get "didn't you say Emma was 7?"The finetune is not a generic model — it is trained on the 17-family / ~874-row gold-standard dataset whose entire purpose is mental-health depth, emotional-support quality, and multi-day handling. That specificity decides exactly what moves into weights and what must stay injected.
[PROFILE]/[FAMILY] — memory gets more load-bearing, not less./context blocks must be byte-compatible with the training renderer (assemble_contexts.py). Single-source them, or the Gemma swap underperforms for a reason no eval will localize.top_k=1, drop a framework only when per-framework ablation proves it.absorb: never.Everything hinges on modules.yaml tagging and /context include-params. One manifest, two targets:
targets:
grok-4.3:
model: x-ai/grok-4.3
context_include: [family, profile, anchors, ledger, honcho, topic]
gemma4-ft:
model: <fireworks-or-local slug>
context_include: [family, profile, anchors, ledger, honcho, topic]
anchors_top_k: 1 # partial absorption (§4.4)
history_adapter: user-only # match training shape — REQUIRED
block_format: assemble_contexts # must match the training renderer
modules:
- id: 00-identity absorb: finetune # voice/persona → training data
- id: 05-safety absorb: never # safety never rides on a finetune
- id: 08-grounding absorb: never # runtime-data protocol is inference-time
- id: 10-voice absorb: finetune
- id: 15-anti-sycophancy absorb: finetune # but see eval note
- id: 20-coaching-foundation absorb: finetune
- id: 30-planning absorb: finetune
- id: 40-telegram absorb: never # channel mechanics, model-agnostic
- id: 50-harness absorb: never # harness behavior, model-agnostic
build_soul.py --target gemma4-ftSOUL.md shrinks ~18 K → ~6 K (safety + grounding + telegram + harness only).
config.yamlmodel.default → the finetune slug.
The pre_llm_call hook reads include/anchors_top_k from the same manifest → the per-turn pack shrinks accordingly.
history_adapter: user-only so the message array matches the trained shape. The one step that is a correctness requirement, not a tuning knob — skipping it silently degrades the finetune.
/context, writeback, JWTs, sessions, Telegram all identical. Rollback = rerun with --target grok-4.3 (adapter off).
absorb: never modules and every category-(B) block are invariant across targets — a family's live roster, profile, inferences and ledger are always injected, any model, forever. absorb: finetune is a claim verified per module by ablation, not a promise (expect anti-sycophancy to need a residual 3-line reminder). And because heavy-Hermes transcripts are the finetune's training data, the loop closes: the better heavy Hermes runs, the better the finetune that replaces its static half.
A real family from loop-phase0 — the Reyes–Whitfield high-conflict co-parenting arc. Day 5. Danielle has spent four days rebuilding trust in systems over promises; here the wound reopens.
user_id 8040940969
Profile High C · anxious-preoccupied · post-betrayal documenter
Arc high-conflict custody
chat_id resolves to user_id 8040940969, per-sender JWT OIMY_JWT_TOKEN_8040940969 minted. Before the model is called, the pre_llm_call hook fetch_grounding.sh fires./context runs the GPT-5.4-mini router (skill_hint: emotional-support, confidence 0.88), calls h30.update_constraint_ledger(), then retrieves the top anchors with the emotional-support skill-boost applied.
// request body from fetch_grounding.sh
{ "user_id": "8040940969",
"message": "Marcus just texted that he can't do...",
"include": ["family","profile","anchors","ledger","honcho","topic"],
"max_bytes": 16000 }[FAMILY — people in Danielle's household & co-parenting orbit]
Danielle Whitfield — parent (primary user)
Theo — son, 8, asthma (rescue inhaler travels in his bag)
Marcus — ex-husband, co-parent (parenting-plan channel only)
Priya — Marcus's fiancée
// hard rule: never invent a name not in this block[Psych] DISC: High C | attachment: anxious-preoccupied
| Hughes need: reliability / to-be-taken-seriously
[Inferences] (Opus, top-5)
· trusts systems > promises since the betrayal (conf 0.82)
· builds-a-case under threat; re-reads own texts 6–7× (0.79)
· day-4: self-caught the loop, re-read once — regulation gain (0.71)
[H3.0] engagement: high | domain: co-parenting | BCT gate: validate-first
[Failed Approaches]
· day-2 "bridge-pending" defer (went quiet, made her chase) — DO NOT repeat### anchor: self-worth-not-contingent-on-their-reliability (score 0.71)
CORE INSIGHT: the ache isn't "I keep hoping" — it's self-blame for
having hoped. Separate his unreliability (his pattern) from her
worth (not on trial here).
DELIVERY [High C + anxious-preoccupied]: name the competence she
already showed; give her one concrete lever, not reassurance-spray.
IF PRIOR DIDN'T LAND: do NOT defer or "sit with it" (see Failed).
anchors_delivered: ["self-worth-not-contingent-on-their-reliability"][CONSTRAINT LEDGER — HARD RULES]
- schedule changes go through the parenting-plan channel, not side-texts
- Theo's rescue inhaler travels in his bag every handoff
[HONCHO] recurring dialectic: she reads logistics failures as
evidence about her judgment, then over-documents to feel safe.
[CURRENT TOPIC] Marcus cancelled weekend, 2 days' notice; plans already set.system = the compiled SOUL.md (identity + safety + grounding protocol + distilled voice + coaching foundation + planning + telegram + harness, ≈18.2 KB) followed by the injected [FAMILY]…[ANCHORS]…[LEDGER] pack — plus Hermes' own last-20 session window and its full toolset.[Inferences].
deep_profile.store_message, the h30 ledger/openings update, and a Honcho append — and marks the anchor delivered so it won't repeat this session. The deep-profile pipeline (Opus) sees the turn, so tomorrow's [Inferences] stay current. Row tagged source="hermes-heavy" for later finetune export.Ordered. P0–P2 are the minimum honest test. Estimates assume familiarity with api.py.
hermes profiles create oimy-heavy — keep the per-sender JWT env pattern from oimy-dev verbatim.config.yaml: model.default: x-ai/grok-4.3, keep fast: gpt-5.4-mini, tool_search on.hermes-heavy/{modules.yaml, build_soul.py, soul.d/}. 00/15/40/50 are cut-and-organize from the SOUL backup; 20 is the anchors header; 30 a fresh ~40-line distillation of ConstraintFitScorer.10-voice: distill combined_v2 40 K → 8 K, keeping message-reading, emotions-first, venting, pacing, opener bans, memory-weaving, never-do list. (Fable drafts; needs Bharath's read.)build_soul.py: concatenate per target, hard-fail over 19,500 chars, stamp header, --check for source-drift warnings.POST /context: plumbing around existing calls (get_context_summary, _build_profile_context, anchor_retrieval, h30 ledger, Honcho, topic). Reuse the JWT check from /chat.POST /context/writeback: store both messages through the same post-response paths, mark anchors_delivered, tag rows source="hermes-heavy".curl /context for both user_ids; verify blocks match what a /chat turn logs today.fetch_grounding.sh: read hook-stdin JSON, resolve sender→user_id→JWT, POST /context, print {"context": ...}. Config: hooks: block with pre_llm_call, hooks_auto_accept: true.oimy-grounding one: "context arrives automatically; do NOT call /chat; you compose the reply."/chat, light Hermes, heavy Hermes. Score with the engine's existing checks (opener repetition, clinical leak, roster hallucination, ledger violations, venting protocol) run offline as judges.absorb claims from "intended" to "verified."source="hermes-heavy", quality-filtered) → finetune training set.vector_memory (fastembed port) — benefits every configuration./chat router/companion path, or prod (:8080) until dev has soaked.What could disappoint, and which calls are Bharath's product judgment rather than an engineering default.
The wire protocol documents pre_llm_call context injection and the config plumbing exists — but a live hook has not been executed under the Telegram gateway here. Verify first in P3 before building anything downstream. Fallback: a tiny local proxy in front of OpenRouter that injects the pack — uglier, model-agnostic, guaranteed.
Anchors and all (B) data are single-sourced by construction. The genuine risk is 10-voice.md — a distillation, i.e. a second artifact. Mitigations: build_soul.py --check mtime alarm; the offline judge suite. Not fully solvable — a distillation is an editorial act. Decide deliberately who owns it.
Engine turns get post-generation enforcement; heavy-Hermes turns get prompt rules plus a stronger model. The engine measured 1.7–5.6% clinical leak at 12B; frontier should be far lower but is not zero. Option: add a validation pass in writeback that flags (not blocks) violations.
Bharath's product call/context marks anchors delivered when served, but Grok may not use a served anchor — over-marking suppresses a framework never delivered. V1 accepts this (conservative is safe per "wrong anchor worse than none"). V2: writeback adds a cheap "anchor actually used?" judgment.
Both layers can initiate — two initiators = double-texting the family. V1: disable engine-side proactive for heavy-profile users; Hermes owns rhythm, using /context for content.
Per turn: one GPT-5.4-mini router call + local retrieval, then one Grok 4.3 completion with ~16 K injected + ~18 K SOUL + tool schemas. Roughly comparable to today's engine path, slightly heavier on input tokens. Fine for a test rig; at scale the finetune swap is the answer by design.
The per-sender JWT scheme is inherited as-is; any new family member needs a minted token + env var. Fine for the test cohort; a /provision flow is needed before any wider pilot.
The finetune's training scope contains zero tool-call turns — it is a conversational companion, not a harness driver. Post-swap: either (a) Gemma-FT is the core and the fast: gpt-5.4-mini slot handles tool orchestration (a two-model harness), or (b) tool-heavy turns route away from Gemma-FT. The architecture supports both, but the choice changes what 50-harness says per target.
Heavy Hermes makes the Hermes layer the product brain and demotes the engine to a knowledge service; the engine roadmap makes Hermes a dumb pipe. Both are now real, testable configs sharing one knowledge base — but they are competing theses about where OiMy's soul lives, and P4 only measures response quality, not strategy (on-device story, unit economics, moat). This document builds the comparison rig; it does not pick the winner.
Bharath's call, with dataoimy-engine-dev.service, port 8090, COMPANION_MODEL=x-ai/grok-4.3, ROUTER_MODEL=openai/gpt-5.4-mini, FALLBACK_COMPANION_MODEL=anthropic/claude-haiku-4-5, DEEP_INFERENCE_MODEL=…/claude-opus-4.8. Prod identical minus PORT/prompt-file.oimy-dev, current model.default: moonshotai/kimi-k2.6, tool_search threshold 5%, context_window: 20, state.db with FTS.CONTEXT_FILE_MAX_CHARS = 20_000 — prompt_builder.py:819. Hook injection {"context": ...} — shell_hooks.py docstring.ANCHOR_THRESHOLD=0.50, ANCHOR_TOP_K=3, ANCHOR_BYTE_CAP=12288, SKILL_BOOST=0.06, DELIVERED_DEMOTION=0.15 — anchor_retrieval.py.api.py:_build_profile_context (~4041). Entity cap 600 chars w/ never-invent-names — api.py:~4818.POST /chat, GET /profile/*, /verify/*, /gap/* — no /context yet.