Persona fine-tuning — your instance’s voice in the weights
Furoshiki already shapes personality at runtime — identity shell, emotions, needs, memory retrieval, trait observations, and situational roles all land in the prompt every turn. Persona FT is the complementary track: a local LoRA style adapter trained on real exchanges so the base model’s default prose habits bend toward how this Furoshiki actually talks on Telegram. Facts, user profile, mood, and RAG stay out of the weights; they continue to flow through SQLite, Chroma, and prompt assembly.
Goal
Build a per-instance conversational style model that operators can refresh as new gold replies accumulate. The system should:
- Ground training targets in real assistant messages (or operator-edited gold), not free-text self-observations alone.
- Keep a human-in-the-loop curation queue so bad or off-brand turns never silently enter a train set.
- Retrain from the base instruct checkpoint every cycle — never stack adapter-on-adapter, so drift stays bounded.
- Gate publish with held-out test loss vs the current champion adapter before Ollama registration.
- Plug into the existing stack as one model tier (e.g.
ollama::furoshiki-persona:latest) while the rest of Furoshiki stays unchanged.
Where it fits in Furoshiki
Runtime personality (every message)
identity_shell— non-negotiable boundaries- Emotions, needs, behavioral cues, memory blocks
record_personality_observation→ trait aggregation → directivespersonality_roles— situational band-1 masksuser_facts, commitments, operator corrections
Updates continuously; no GPU required.
Persona FT (periodic, local)
persona_ft_examplesqueue (pending → promoted → used)- Export → MLX QLoRA on Apple Silicon
- Eval vs
champion/adapter - Publish Modelfile to Ollama
Improves phrasing priors; does not store new facts.
Over months, the prompt layer tells the model what matters today; the LoRA layer makes the same instructions sound more like the relationship you have already built — shorter clauses, warmth, directness, humor timing — without duplicating Chroma into weights.
End-to-end process
CAPTURE Telegram /persona keep · /persona capture on │ Post-conversation LLM mining (source=automatic, quality-gated) │ Linked exports from personality tool logs + curated session logs ▼ CURATE Dashboard Persona FT or CLI pending/promote/reject/edit │ Rows: situation + user line + assistant gold (+ trait / style note) ▼ EXPORT furoshiki persona-ft export → cycles/<id>/data/{train,valid,test}.jsonl │ Chat template: fixed system prompt + structured user brief + assistant reply ▼ TRAIN mlx_lm LoRA on base (default: Llama 3.2 3B Instruct 4-bit) │ Small-data hyperparams (low LR, ~200 iters, rank 8) ▼ EVAL Test loss vs champion; optional commitment quotes as human gold ▼ PUBLISH Fuse adapter → Ollama tag (default furoshiki-persona) │ Promoted rows → status used for this cycle ▼ RUN Point a model tier at ollama::furoshiki-persona:latest Full prompt pipeline unchanged — adapter only affects token sampling.
Capture sources
| Source | How it enters the queue | Typical status |
|---|---|---|
| Telegram | /persona keep [trait] on a reply; /persona capture on auto-queues each turn until off |
pending → operator promotes |
| Post-conversation mining | After quiet-window reflection, rubric nominates high-signal style exemplars (confidence + optional authentic/grounded floors) | pending, source=automatic |
| Export-time joins | record_personality_observation tool log matched to session logs; structured self_observations with curations; curations.jsonl overrides |
Merged into JSONL at export (not always a DB row) |
| Eval / test | Honored behavioral_commitments with quoted gold in evaluation_notes |
test.jsonl — not trained |
What each training example looks like
Examples are mlx-lm chat JSONL triples. The system message is a fixed persona training prompt
($FUROSHIKI_DATA/persona_ft/system_prompt.txt, must match Ollama SYSTEM after publish).
The user message is a structured brief — situation, trait focus, optional style note (explicitly “do not recite”),
sometimes a behavioral commitment — ending with “Respond in character — one short message.”
The assistant target is the real or curated Telegram reply (clipped to a sane length), never the observation prose itself.
Curation and lifecycle (persona_ft_examples)
Every accumulating row has a named downstream — no dead-end table:
| Status | Meaning | Exit |
|---|---|---|
pending | Captured or mined; needs review | Promote, reject, edit, or delete |
promoted | Approved for export | Included in next export; becomes used after successful publish cycle |
rejected | Operator discarded | Terminal |
used | Consumed by a published cycle | Historical record; new captures needed for next train |
Dashboard Persona FT and repair API routes mirror the CLI: bulk promote/reject, inline edit of situation and messages, manual insert for gold pairs. This is the quality gate that keeps small-data LoRA from memorizing one bad evening.
Train, eval, publish
Artifacts
All under $FUROSHIKI_DATA/persona_ft/:
config.json— base model, LoRA knobs, Ollama tag, eval ratio capcycles/<id>/data/— train / valid / test JSONL + manifestcycles/<id>/adapters/— LoRA weights for the cyclechampion/— last adapter that passed eval (metadata inmeta.json)
Defaults (operator-tunable)
- Base:
mlx-community/Llama-3.2-3B-Instruct-4bit - LoRA rank 8, alpha 16, learning rate
5e-6, ~200 iterations (anti-overfit for small sets) - Publish only if test loss ≤ champion loss ×
max_test_loss_ratio(default 1.02), unless forced
CLI
| Command | Role |
|---|---|
furoshiki persona-ft status | Workdir, cycles, champion summary |
furoshiki persona-ft export | Build JSONL for a cycle |
furoshiki persona-ft train | Run mlx_lm LoRA |
furoshiki persona-ft eval | Test loss vs champion |
furoshiki persona-ft publish | Fuse + ollama create |
furoshiki persona-ft cycle | export → train → eval → publish (one shot) |
furoshiki persona-ft pending|promote|reject|drop | Queue management without dashboard |
Prerequisites: Apple Silicon Mac with sufficient unified memory (32 GB comfortable for 3B QLoRA),
pip install -r requirements-persona-ft.txt, optional Ollama for publish.
Canonical operator doc in the repo: docs/PERSONA-FT.md.
How this improves personality over time
- Weeks 1–2 — prompt-only. Trait observations, roles, and memory do the heavy lifting; you capture standout Telegram turns with
/persona keep. - First cycle — baseline adapter. After ~dozens of promoted pairs, export + train establishes a champion that tracks your curated voice.
- Ongoing — dual feedback. Runtime systems record what felt authentic (observations, commitments honored); Persona FT records how it was said (assistant strings). Mining adds candidates; you still promote manually.
- Periodic refresh. Each
cycleretrains from base + full promoted corpus (plus export joins), compares eval loss, and swaps Ollama only when the candidate wins — so regression is detectable. - Composability. When you enable the persona tier for live chat, the model still receives the same behavioral cues, memory, and corrections; users perceive tighter alignment because phrasing matches history, not because facts were baked into weights.
Expected outcomes
- Success: Replies feel more like your established Telegram voice with less “generic assistant” cadence, while factual questions still defer to retrieval and tools.
- Not a goal: Replacing OpenRouter/cloud models entirely, storing private facts in the checkpoint, or automating promotion without operator review.
- Failure modes we design against: overfitting tiny sets (low LR, short iters, eval gate), training on observation text (export filters), adapter stacking (full retrain rule), silent quality decay (champion comparison).
Related systems
- Emotion state & needs — still injected every turn; not trained into LoRA.
- System architecture — prompt bands, memory, reflection loops.
- User guide — Persona FT — Telegram commands, dashboard, env toggles for auto-mine.
- Personality evolution (traits, auto-learned roles) — parallel track; see dashboard Personality evolution and
docs/PERSONALITY-EVOLUTION-PLAN.md.