MCP server for customer-persona simulation, councils, and design-research synthesis.
Project description
Sonaloop
Terminal-first, MCP-accessible persona simulation and council system.
Sonaloop models customer profiles as persistent agents with durable
SOUL.md files, timestamped calendars, activity logs, inner thoughts, evidence,
and council-style debates. The web UI is only for inspecting the current state;
all creation, simulation, and council execution happens through CLI or MCP.
Simulation must be non-directional. Profiles are not nudged toward any product thesis unless their own source description, evidence, recent calendar, or explicit task context supports it.
Quickstart — no coding needed
You do not need to be a programmer to run this. The whole system is driven by an AI coding agent — Claude Code (recommended) or OpenAI Codex — that does the setup and operation for you. You just talk to it.
Step 1 — Install an AI coding agent.
Install Claude Code (https://claude.com/claude-code) or Codex and open it in a
terminal or your editor. (Both have plain installers; the agent itself will help
if you get stuck.)
Step 2 — Paste this prompt to the agent. Replace the one line in brackets with what you want to use personas for — it does not have to be architecture/BIM; it can be anything (SaaS onboarding, a hiring panel, a city council, game NPCs, …):
I am not a programmer — please do every step yourself and explain in plain language; ask me before anything destructive.
- Check whether
gitanduv(the Python tool from Astral) are installed; if not, install them for my operating system.- Clone
https://github.com/jhoetter/sonaloopandcdinto it.- Run
uv sync, thencp .env.example .env.- Read
AGENTS.mdandCLAUDE.mdand follow them as your operating rules.- Run
make skillsso the persona skills are discoverable.- My use case is: [describe who you want to simulate and what question you want answered]. Help me write a few persona source prompts for that, create those personas, simulate a bit of their life, then run a council and a synthesis on my question. Walk me through reading the result.
That is the entire onboarding. The agent will create the personas, run the councils, and produce the synthesis report; you read it and decide what to ask next.
Step 3 (optional) — an OpenAI API key. Everything works without any API key. A key only adds two niceties: generated avatar images for personas and semantic memory recall (without it, recall still works on keyword/recency). To add one:
- Go to
https://platform.openai.com/, sign up / log in. - Open Billing and add a small amount of credit (a few dollars is plenty).
- Open API keys → Create new secret key, copy the value (starts with
sk-…). - Open the
.envfile in the project and setOPENAI_API_KEY=sk-…. Save. Done — never share this key or commit it (the repo already ignores.env).
Privacy note for sharing this repo: your own personas, councils and syntheses live under
data/export/,spec/persona-source-prompts.mdandexports/, and are all git-ignored — they stay on your machine and are never pushed. See Data layout & privacy below.
How it works
Four layers build on each other: persona → simulation/memory → council → synthesis. The server gathers context and persists; the host agent authors all text (gather → author → write-back). No server-side text LLM calls.
flowchart TD
SRC["Source prompts - spec/persona-source-prompts.md"] -->|brief_persona then record_persona| P["Persona + SOUL.md"]
P -->|brief_day then record_day| D["Workday: calendar, activities, inner thoughts"]
D -->|brief_consolidation then record_memory_deltas| M[("Memory graph: entities, bi-temporal facts, threads")]
M -->|brief_digest then put_digest| DG["Digests: week / month / quarter / year"]
M -.->|recall_memory and get_state_at time-travel| P
P -->|run-council then record_council| C["Council: turns, votes, exec_summary"]
M -.->|grounds| C
C -->|brief_synthesis then record_synthesis| S["Synthesis report: arc, recommendations, positioning"]
C -.->|synthesize loop suggests next council| C
EVAL["evaluate_simulation + LLM critic"] -.->|gates| D
The gather→author→persist contract every generative step follows:
sequenceDiagram
participant H as Host agent
participant PC as Sonaloop
H->>PC: brief_* gather context
PC-->>H: SOUL + memory + recall + instructions + schema
H->>H: author JSON - text generation happens HERE
H->>PC: put_* / record_* validate and persist
Note over H,PC: day to consolidate to digest, then councils to synthesis
Install (PyPI)
Sonaloop publishes a normal Python package — use it as an MCP server without cloning:
# one-off, no install (recommended for MCP clients):
uvx sonaloop-mcp
# or install the CLI + MCP + web entrypoints:
pip install sonaloop # or: uv tool install sonaloop
sonaloop setup # fetch the headless browser (prototype screenshots + PDF export)
sonaloop info # show resolved data dir, DB path, browser availability
When installed (not a source checkout), all writable state lives in a per-user data
directory (platformdirs — e.g. ~/.local/share/sonaloop on Linux). Override with
SONALOOP_DATA_DIR=/path. Read-only package data (methodology specs, MCP suggestions,
prototype templates) ships inside the wheel. sonaloop setup is optional — screenshots
and PDF export degrade gracefully if the browser is absent.
Setup (from source / development)
flowchart LR
A["git clone"] --> B["uv sync"]
B --> C["cp .env.example .env - OPENAI_API_KEY optional"]
C --> D{"data/export present?"}
D -->|yes| E["make restore - rebuild exact state, no regen"]
D -->|no| F["create personas, simulate, council"]
uv sync
cp .env.example .env
In a source checkout, writable state stays under ./data (as before) so the dev
workflow is unchanged.
Text generation is performed by the MCP host agent, such as Claude Code or
Codex. Sonaloop does not call text LLM APIs and does not use text API
keys; there is no deterministic simulation fallback. OPENAI_API_KEY is used
only for non-text helpers: avatar image generation and embeddings for semantic
memory recall (both optional — without the key, avatars are skipped and recall
falls back to keyword/recency/importance only).
Use sonaloop purge-runtime-data or the MCP purge_runtime_data tool
for a clean slate.
Data layout & privacy
All generated state lives under data/, and all of it is git-ignored and
local-only — nothing here is ever pushed to the public repo. There is exactly
one portable snapshot artifact (data/export/) that round-trips your full
state, and the rest is rebuildable runtime:
data/
sonaloop.db(-wal/-shm) runtime SQLite — source of truth at runtime [gitignored]
personas/<slug>/SOUL.md,MEMORY.md rendered projections (cache) [gitignored]
avatars/<slug>-<hash>.png generated avatar images (cache) [gitignored]
export/ portable snapshot of YOUR state [gitignored · private]
manifest.json, world_context.json
personas/<slug>/ profile.json · SOUL.md · MEMORY.md · avatar.png
calendar.json · experiences.json · daily_summaries.json
memory.json (entities/facts/threads/plans/digests/links) · eval.json
Your persona source prompts (spec/persona-source-prompts.md) and rendered
synthesis reports (exports/) are git-ignored for the same reason — they are
your content, not the engine.
Move your exact state to another machine without regenerating — privately, not through the public repo (e.g. copy the folder, a private archive, or your own private fork):
make snapshot # writes data/export/ — your private, portable state
# ...on the other machine, inside a clone of the repo:
make restore # rebuilds the runtime DB + avatars + SOULs from data/export/
make restore round-trips the full graph deterministically. Embeddings are
re-derived on restore (not stored, to keep snapshots lean); recall stays
keyword-only until that backfill completes.
Running
make dev # web inspector on :8787 (prints → http://127.0.0.1:8787 when ready)
make dev-forwarded # web inspector on :18787 (SSH tunnel friendly)
make mcp # MCP server (stdio)
make skills # symlink claude-skills/* into .claude/skills/ (Claude Code skill discovery)
make snapshot # write private, local-only data/export/
make restore # rebuild runtime DB from data/export/
The inspector and all generated text are bilingual (German/English). The
content language is auto-detected from the language you write in and then
persisted; the UI language has its own toggle in the top bar (?lang=de|en,
or sonaloop set-language). Default is English until you write German.
The web UI is a Linear/Notion-grade inspector (Overview · Personas · Councils · Synthesen in a navigation-only sidebar; a personas card-grid home; Linear-style
list views; the Synthese report as a Notion-style document with table of
contents, properties rail, callouts, toggles and PDF export; each persona's
🧠 Memory page with project timelines, time-travel and recall). It supports
dark mode, a resizable/collapsible sidebar, breadcrumbs and keyboard navigation
(g o/p/c/s, [). It is read-only — all creation happens via CLI/MCP.
Councils, strategies & synthesis
- Council = personas react to a prompt, grounded in their own memory (each can
recallon demand). Run via therun-councilskill; it supports a moderated back-and-forth (a host mediator reads the round and directs who replies next) and pluggable mediator strategies (positive-deepdive,pain-discovery,tension,goal) with a hand-raising convergence loop + upper bound. - Analysis → council loop → synthesis. An analysis is a study with a
question/goal; the
synthesizeskill is its iterative driver: from one statement it runs a council, reads the result and decides whether a follow-up council is worth it — authoring the next self-contained question itself (personas are council-stateless) — until the goal is reached ormax_councils(default 10). The councils are the log; the synthesis is the answer/report. - Synthesis = the report. Besides the cross-council prose (arc, recommendations,
positioning, pain-solvers, segments) it carries a structured per-persona
voiceslayer: each persona'ssentiment,relevance(how much the topic touches their work), the one-line key argument (why), a shift (e.g. neutral→positiv with the triggering argument), and grounded evidence quotes. The web report (/syntheses/{id}) is answer-first with an interactive Stimmen panel (filter/sort by sentiment & relevance, expand for shift + evidence); councils sit underneath as the log.export_synthesis(md/json) is self-contained for handing to a downstream agent.
Skills live in claude-skills/ (simulate-cohort,
run-council, synthesize); run make skills once so Claude Code discovers them.
Claude MCP
Add Sonaloop as an MCP server in Claude Desktop or any Claude MCP client.
Installed from PyPI (no checkout needed):
{
"mcpServers": {
"sonaloop": {
"command": "uvx",
"args": ["sonaloop-mcp"]
}
}
}
Or, from a source checkout (development):
{
"mcpServers": {
"sonaloop": {
"command": "uv",
"args": ["run", "sonaloop-mcp"],
"cwd": "/absolute/path/to/sonaloop"
}
}
}
Claude should use MCP tools such as prepare_persona_agent_context,
brief_persona/record_persona, brief_day/record_day, and
brief_council/record_council; persona-facing work must load
SOUL.md through prepare_persona_agent_context. The host agent authors text
content directly and submits structured JSON through MCP. OPENAI_API_KEY is
only needed for non-text helpers: generate_avatar and embeddings for semantic
recall_memory (recall still works without it, keyword/recency-only).
Agent and MCP operating instructions live in AGENTS.md. Claude should follow CLAUDE.md, which delegates to the same instructions.
The single project tracker — spec, reference, and the Outstanding Work list —
is SPEC_TRACKER.md. The supporting architecture and contracts
live under spec/:
memory-and-simulation-architecture.md,
mcp-tool-contract.md,
simulation-loop-contract.md. Your own persona
source prompts live in spec/persona-source-prompts.md (git-ignored, local-only)
— it is the canonical source from which personas are (re)built; write yours there.
Credits
The council format was inspired by Leo Püttmann's
ai-council — its
markdown-defined agents and select → debate → propose → vote → persist flow
seeded this project. Sonaloop takes it further with durable persona state,
persistent memory, longitudinal simulation, and MCP-host-authored text. See
SPEC_TRACKER.md for the
full reference analysis.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sonaloop-0.1.0.tar.gz.
File metadata
- Download URL: sonaloop-0.1.0.tar.gz
- Upload date:
- Size: 307.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11a50e866431339909559020c2ff39a75ffe076315b8a44a82642d8ff7927195
|
|
| MD5 |
e97e55f3772f7c18d4e7db1dd8e42a81
|
|
| BLAKE2b-256 |
68801366537a7eb91f1a3fa22da1db50274a730a9e4d55e2e32ef8e250bae16b
|
File details
Details for the file sonaloop-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sonaloop-0.1.0-py3-none-any.whl
- Upload date:
- Size: 373.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.15 {"installer":{"name":"uv","version":"0.11.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb70a1f933c8986a0dc4de411012c30ba9975a387382019119813e85355467da
|
|
| MD5 |
186b4fb538943b12883d1e705704797c
|
|
| BLAKE2b-256 |
df6a68ee5fa5db23914b9251d0c6a46c4e9df84482df55496baebb5094d7c602
|