Skip to main content

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.

  1. Check whether git and uv (the Python tool from Astral) are installed; if not, install them for my operating system.
  2. Clone https://github.com/jhoetter/sonaloop and cd into it.
  3. Run uv sync, then cp .env.example .env.
  4. Read AGENTS.md and CLAUDE.md and follow them as your operating rules.
  5. Run make skills so the persona skills are discoverable.
  6. 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:

  1. Go to https://platform.openai.com/, sign up / log in.
  2. Open Billing and add a small amount of credit (a few dollars is plenty).
  3. Open API keys → Create new secret key, copy the value (starts with sk-…).
  4. Open the .env file in the project and set OPENAI_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.md and exports/, 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 recall on demand). Run via the run-council skill; 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 synthesize skill 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 or max_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 voices layer: each persona's sentiment, 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sonaloop-0.1.0.tar.gz (307.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sonaloop-0.1.0-py3-none-any.whl (373.0 kB view details)

Uploaded Python 3

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

Hashes for sonaloop-0.1.0.tar.gz
Algorithm Hash digest
SHA256 11a50e866431339909559020c2ff39a75ffe076315b8a44a82642d8ff7927195
MD5 e97e55f3772f7c18d4e7db1dd8e42a81
BLAKE2b-256 68801366537a7eb91f1a3fa22da1db50274a730a9e4d55e2e32ef8e250bae16b

See more details on using hashes here.

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

Hashes for sonaloop-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fb70a1f933c8986a0dc4de411012c30ba9975a387382019119813e85355467da
MD5 186b4fb538943b12883d1e705704797c
BLAKE2b-256 df6a68ee5fa5db23914b9251d0c6a46c4e9df84482df55496baebb5094d7c602

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page