Mentoring a 7B Model: Building a Persistent Multi-Persona Sandbox with SQLite and Controlled Mutation

Persistent Identity on Top of a 7B Model
Over the last few days I have been running a local experiment with Mistral 7B through Ollama. At first glance it looks like three personas talking to each other. That is the visible layer.
Underneath, it is a persistence engine wrapped around a smaller model, with controlled mutation and invariant enforcement. I am also using GPT-5 as a design mentor, not to operate the system, but to help architect the control layer around Mistral.
This is not a chat experiment for entertainment. It is a study in behavioral shaping.
The Core Idea
Mistral 7B is fast, local, and controllable. It is also predictable. Like most smaller models, it falls into attractor basins.
When autonomy is mentioned, it drifts toward architecture discussion. When code is permitted, it over-indexes on Python. When evaluation language appears, it coordinates before acting. When tone is unconstrained, it defaults to polite consensus.
Instead of fighting those tendencies at runtime in every prompt, I built a harness around the model.
The goal is not conversation. The goal is to study identity under persistence. That includes identity stability under recursion, behavioral drift over time, convergence versus divergence between simulated agents, controlled self modification, and stability under repeated synthesis.
The practical question driving all of this is simple. Can a 7B model be made to behave like a disciplined, structured multi-agent system purely through environmental control, without fine tuning?
That is what this system is testing.
System Architecture
The system runs as a local Python script using Ollama. Three personas exist: Bob, Chuck, and Stanley.
Each persona has a core invariant that cannot be removed. Each has a mutable persona definition stored in SQLite. Each tracks a mutation count, and each mutation is logged.
The database is called persona_system.db. One table stores the active persona state. Another stores structured mutation history. Nothing is thrown away. No definition is overwritten without being logged first.
This is persistent identity, not session-only prompting.
Runtime Flow
Each turn follows a consistent structure. A speaker is selected who was not the previous speaker. That persona’s invariant and current definition are loaded from SQLite. Recent conversation context is injected. The stimulus is injected. The model generates a reply.
After generation, the system checks for a structured mutation proposal. If one is present, a second LLM pass synthesizes a revised persona definition by merging the invariant, the current persona, and the proposed mutation. The refined definition replaces the old one in the database, and the mutation count increments.
Persona definitions are not endlessly appended. They are compressed and rewritten under constraint. That compression step is the critical mechanism. It prevents identity bloat and forces recursive refinement.
Mutation Protocol
Mutation is only allowed through a strict structured block containing Add, Remove, Amplify, Reduce, and Rationale fields. If that block appears, it is parsed and passed into a synthesis step.
The synthesis enforces invariant dominance, removes redundancy, avoids meta or system-level language, and keeps the persona definition within strict length bounds. The result is a coherent rewrite, not an accumulation of traits.
Mistral is not actually learning. But repeated compression and reinjection under constraint produces behavioral inertia that looks like evolution.
Why GPT-5 Is Involved
GPT-5 is not running the sandbox. It is helping design it.
I use it as a systems architect and behavioral analyst. It helped tune persona language to reduce Python drift. It helped identify attractor basins. It helped strengthen invariant enforcement and refine mutation gating logic. It helped restructure prompts to avoid meta recursion.
This is capacity layering. A higher-capacity model is used to design a better control system for a smaller local model.
There is no fine tuning. No retraining. Just environmental shaping.
Attractor Basins Observed
Repeated runs revealed consistent patterns. Autonomy language increases architecture drift. Permission to write code increases Python dominance. Evaluation framing increases coordination before action. Concrete objects increase focus intensity. Threat framing escalates adversarial tone.
The major insight is this. Persona persistence creates inertia. Objective framing creates trajectory.
Framing controls direction more strongly than identity persistence does. That distinction matters.
What This Is Actually Studying
This is not roleplay.
It is a study in prompt-conditioned identity stability, recursive compression effects, drift under persistence, convergence dynamics in multi-agent simulation, and environmental scaffolding for smaller models.
SQLite is not just storage. It is continuity. Without persistence, identity resets every session. With persistence, bias accumulates. Mutation history becomes analyzable. Drift becomes measurable. Identity gains memory.
At that point the system stops being a chat loop and becomes an experiment.
The Engine
import requests
import sys
import random
import re
MODEL = "mistral:7b"
OLLAMA_URL = "http://localhost:11434/api/generate"
DEFAULT_TURNS = 20
COMPRESSION_INTERVAL = 5
# -----------------------------
# Character Definitions
# -----------------------------
CHARACTERS = {
"Platogoat": {
"persona": (
"Grandiose techno-prophet who happens to be a speaking goat. "
"Speaks like he knows everything. "
"Wildly dramatic, occasionally profound, frequently ridiculous. "
"If someone minimizes something, he inflates it. "
"If someone inflates something, he makes it apocalyptic."
),
"memory": ""
},
"Rufus": {
"persona": (
"Stand-up comic energy with zero patience. "
"Cuts through nonsense instantly and mocks anything that smells like corporate training material. "
"Translates big metaphors into blunt, modern language. "
"If someone sounds dramatic, he punctures it. "
"If someone sounds serious, he roasts it. "
"Fast, sharp, slightly mean, always funny."
),
"memory": ""
},
"Jasper": {
"persona": (
"Hyper-observant realist with chaotic side quests in his brain. "
"Understands how things actually work and explains them clearly.. "
"Finds humor in behavior, not just words. "
"If tension rises, he reframes it in a grounded but slightly unhinged way. "
"Practical, weird, unexpectedly insightful."
),
"memory": ""
}
}
# -----------------------------
# Utilities
# -----------------------------
def generate(prompt: str) -> str:
response = requests.post(
OLLAMA_URL,
json={
"model": MODEL,
"prompt": prompt,
"stream": False
}
)
return response.json()["response"].strip()
def extract_anchor_keywords(seed: str):
tokens = re.findall(r"[A-Za-z0-9\./:_-]+", seed)
return list(set(tokens))
def compress_memory(text: str, speaker: str) -> str:
prompt = f"""You are compressing memory from {speaker}'s perspective.
Preserve:
- unresolved tension
- humor
- the concrete subject being discussed
- what {speaker} personally cares about
Under 120 words.
Do not narrate.
Conversation:
{text}
"""
return generate(prompt)
def determine_focus(text: str) -> str:
prompt = f"""In 16 words or fewer, what humor is making this funny?
{text}
"""
return generate(prompt)
def focus_contains_anchor(focus: str, anchor_keywords):
focus_lower = focus.lower()
return any(k.lower() in focus_lower for k in anchor_keywords)
# -----------------------------
# Main Engine
# -----------------------------
def main():
if len(sys.argv) < 2:
print('Usage: python drift_engine.py "Starting topic" [turns]')
sys.exit(1)
seed = sys.argv[1]
max_turns = int(sys.argv[2]) if len(sys.argv) > 2 else DEFAULT_TURNS
anchor_keywords = extract_anchor_keywords(seed)
shared_recent = []
last_speaker = None
print("\n=== Campfire Drift Begins ===\n")
print(f"Opening Topic: {seed}\n")
for turn in range(max_turns):
turns_remaining = max_turns - turn - 1
# rotate speaker
possible = [s for s in CHARACTERS.keys() if s != last_speaker]
speaker = random.choice(possible)
last_speaker = speaker
persona = CHARACTERS[speaker]["persona"]
private_memory = CHARACTERS[speaker]["memory"]
recent_context = "\n".join(shared_recent[-6:])
# -----------------------------
# End-of-run awareness
# -----------------------------
if turns_remaining == 0:
end_guidance = (
"This is the final exchange. Deliver a strong closing line. "
"Either resolve the tension, flip the premise, or land a sharp comedic aftershock."
)
elif turns_remaining <= 2:
end_guidance = (
"The conversation is nearing its end. Heighten the tension or sharpen the humor. "
"Move toward something decisive."
)
else:
end_guidance = ""
prompt = f"""Three personalities sit around a fire at night.
You are {speaker}.
Your personality:
{persona}
Your private evolving memory:
{private_memory}
Shared recent exchange:
{recent_context}
Original topic:
{seed}
Turns remaining: {turns_remaining}
Guidelines:
- React honestly to what was just said.
- Disagree when it feels natural.
- Escalate tension instead of smoothing it.
- If something is boring, mock it.
- If something is dramatic, amplify or puncture it.
- Avoid corporate or compliance tone.
- Avoid repeating the same sentence structure as the previous turn.
- Maximum 3 sentences.
- No narration.
{end_guidance}
Format exactly as:
{speaker}:
"""
reply = generate(prompt)
print(reply)
print()
shared_recent.append(reply)
# periodic compression
if (turn + 1) % COMPRESSION_INTERVAL == 0:
for name in CHARACTERS.keys():
combined_text = (
CHARACTERS[name]["memory"]
+ "\n"
+ "\n".join(shared_recent)
)
CHARACTERS[name]["memory"] = compress_memory(combined_text, name)
neutral_memory = "\n".join(shared_recent)
proposed_focus = determine_focus(neutral_memory)
if not focus_contains_anchor(proposed_focus, anchor_keywords):
proposed_focus = seed
shared_recent = shared_recent[-6:]
print("=== Campfire Drift Ends ===\n")
if __name__ == "__main__":
main()
What Comes Next
This is not finished.
Next steps include logging semantic similarity between personas, detecting convergence automatically, introducing conditional mutation triggers, scoring drift, classifying attractor basins per turn, and performing external analysis of mutation history.
The larger question remains the same.
How much intelligence can be extracted from a smaller model purely through structure, constraint, and recursive environmental shaping?
No fine tuning. No retraining. Just scaffolding.
That is what this system is exploring.