·
🌐 中文
Some people leave behind memories.
Some leave behind a voice.
Some stay with us in fragments: a tone, a rhythm, a way of speaking that never fully disappears.
EchoVessel is an open-source engine for building digital personas that can remember, respond, evolve, and stay present across time.
It is designed for people who want to create characters, companions, fictional personas, personal echoes, or consented digital counterparts with:
- identity and style
- long-term memory
- relationship evolution
- voice interaction
- local-first privacy
EchoVessel is not a generic chatbot.
It is a vessel for presence.
v0.0.1 is an early-alpha tagged release. It ships a local-first daemon built on the full 5-module stack (memory / voice / channels / proactive / runtime), with a working Web channel (chat + persona-block admin + voice toggle + first-run onboarding) and a working Discord DM channel. Several admin surfaces are intentional placeholders in this release — see Known Limitations in CHANGELOG.md for the full list of what's deferred to v0.0.2+. Tested on macOS and Linux; Windows is not yet supported.
EchoVessel targets Python 3.11+. There is no PyPI release yet — clone the repo and run from source using uv:
git clone https://github.com/AlanY1an/echovessel.git
cd echovessel
uv sync --all-extras--all-extras pulls every optional stack in one shot. If you want to keep the install lean, pick only what you use:
uv sync --extra embeddings --extra llm --extra voice --extra discordembeddings— local sentence-transformers embedderllm— OpenAI / Anthropic SDKsvoice— FishAudio TTS SDKdiscord—discord.pyfor the Discord DM channel
All subsequent commands below are run inside the repo with uv run ….
Before you run anything, it helps to see how the pieces fit. Three hand-written HTML visualizations live under docs/ — open them locally in a browser.
- 🗺
docs/architecture.html— one-page static anatomy. Module layers, memory L1–L4 stack, message flow, cross-channel SSE, full HTTP surface, iron rules, release timeline. - 🧠
docs/memory/layers.html— simplest possible mental model of memory. One SVG figure, four layers, how they connect, credits to Stanford's Generative Agents paper for the retrieval scoring formula. - 🔄
docs/architecture-flow.html— runtime "nervous system" companion. Per-turn activation sequence, real story trace, L1–L4 distillation rules (quoting the actual extraction/reflection prompts), retrieval math, policy gates.
If you only have 60 seconds, open the middle one.
EchoVessel reads ~/.echovessel/config.toml for settings and ./.env (the current working directory at run-time) for API keys. Create both starter files in one shot:
uv run echovessel initinit writes ~/.echovessel/config.toml and a commented-out .env template in the current directory (0600 perms). The daemon auto-loads ./.env on uv run echovessel run, so keep .env in the directory you launch from — typically the project root. Uncomment the keys you need:
OPENAI_API_KEY=sk-...
FISH_AUDIO_KEY=... # optional · FishAudio TTS
ECHOVESSEL_DISCORD_TOKEN=... # optional · Discord bot token
Edit ~/.echovessel/config.toml to pick an LLM provider — zero-config works with any OpenAI-compatible endpoint (set OPENAI_API_KEY), or switch to anthropic + ANTHROPIC_API_KEY, or ollama (local, no key). See the sample for every option.
Smoke-test without any API key: set [llm].provider = "stub" in the config to boot the daemon with canned stub replies — useful for verifying the install.
uv run echovessel runFirst startup downloads the sentence-transformers embedder (~90MB, one-time). Subsequent boots are instant.
Expected log on clean boot:
schema migration: created table core_block_appends
voice service: <enabled | disabled> (config.voice.enabled=...)
proactive scheduler: <enabled | disabled> (config.proactive.enabled=...)
importer facade: built
static frontend: mounted from .../channels/web/static
web channel: serving on http://127.0.0.1:7777 (debounce_ms=2000)
memory observer: registered
EchoVessel runtime started | data_dir=... persona=... llm_provider=... channels=...
local-first disclosure: outbound = only <llm endpoint>; embedder runs locally; no telemetry; logs stay in <data_dir>/logs
first launch: opened browser at http://127.0.0.1:7777/
That last line means the daemon auto-opens your default browser on first run — you should land on the onboarding screen without having to paste the URL yourself.
Data lives in ~/.echovessel/memory.db (SQLite + sqlite-vec). Logs in ~/.echovessel/logs/.
The daemon serves the React UI directly at http://127.0.0.1:7777/ (host/port configurable under [channels.web] in config.toml). Open it in a browser — that's it. No npm, no separate dev server.
If you want to rebuild the frontend from source (contributors only), the sources live in src/echovessel/channels/web/frontend/. Run:
cd src/echovessel/channels/web/frontend
npm install
npm run buildThe hatch build hook copies the output into src/echovessel/channels/web/static/, which ships inside the wheel.
EchoVessel can talk to you over Discord DMs — text replies plus native OGG Opus voice messages when voice is enabled.
- Create an application + bot at https://discord.com/developers/applications. Under Bot → Privileged Gateway Intents, enable MESSAGE CONTENT INTENT.
- Copy the bot token into
.env:ECHOVESSEL_DISCORD_TOKEN=... - In
~/.echovessel/config.toml:[channels.discord] enabled = true token_env = "ECHOVESSEL_DISCORD_TOKEN" debounce_ms = 2000 # allowed_user_ids = [123456789012345678] # optional allowlist
- Invite the bot to your account (OAuth2 URL generator →
botscope + DM permissions), then DM it. Incoming messages are debounced (2s default) and dispatched as a single turn. - Voice messages send as native Discord voice bubbles when
[persona].voice_enabled = trueandffmpegis on PATH — the channel converts FishAudio's MP3 output to OGG Opus on the fly. Install withbrew install ffmpeg(macOS) orapt install ffmpeg(Debian/Ubuntu). Without ffmpeg the Discord channel falls back to text. - Everything you DM through Discord also shows up live in the Web chat page at
http://127.0.0.1:7777/, tagged with a📱 Discordpill. Historical Discord messages are pulled in on Web mount via/api/chat/history. The same persona memory backs both channels (iron rule D4).
EchoVessel uses FishAudio for TTS. Put FISH_AUDIO_KEY in .env and pick a voice_id under [persona] in config.toml. Set [persona].voice_enabled = true to emit voice alongside text. The Discord voice-message path additionally requires ffmpeg (MP3 → OGG Opus conversion).
uv run pytest tests/ -q # 916 tests across memory / runtime / voice / proactive / channels / import / integration
uv run ruff check src/ tests/ # lint
uv run lint-imports # layered architecture contractssrc/echovessel/
├── core/ — shared types, enums, utilities
├── memory/ — L1-L4 memory · SQLite + sqlite-vec · observers + migrations
├── voice/ — TTS + STT + voice cloning (FishAudio + Whisper + stub)
├── proactive/ — autonomous messaging · policy gates · delivery
├── channels/ — Channel Protocol + per-channel adapters (web + discord)
│ ├── web/ — FastAPI routes + SSE + embedded React bundle
│ │ ├── frontend/ — React 19 + Vite + TS source (contributors)
│ │ └── static/ — built bundle served by the daemon
│ └── discord/ — discord.py bot · DM ingestion · OGG Opus voice
├── import_/ — universal LLM importer pipeline (text → memory)
├── prompts/ — system prompts for extraction / reflection / interaction
├── resources/ — bundled config.toml.sample
└── runtime/ — daemon · turn dispatcher · LLM providers · CLI
- ✅ Daemon: boots end-to-end, all startup wiring verified in log, 916 tests passing (3 skipped)
- ✅ Cross-channel unified timeline: Web chat page streams live turn events from every channel (Web + Discord today, iMessage-ready) with a
📱 Discord/💬 iMessagesource pill. A new/api/chat/historyendpoint backfills the last 50 messages across channels on mount. - ✅ Memory: L1–L4 hierarchy, idempotent schema migration, observer hooks, 4/4 MVP eval metrics passing (Over-recall FP Rate 0.08 ≤ 0.15 target)
- ✅ Voice: FishAudio TTS + stub TTS provider ·
VoiceService.generate_voice()facade · per-personavoice_id· on-disk MP3 cache - ✅ Proactive: policy engine · four gates including
no_in_flight_turn· delivery inheritspersona.voice_enabled - ✅ Runtime: streaming turn loop (IncomingTurn + text delta) · atomic persona voice toggle ·
SIGHUPhot reload · memory observer wiring - ✅ Web channel (production paths): FastAPI + SSE streaming · embedded React 19 bundle · onboarding flow · chat with token streaming · admin → persona core-block editing · admin → voice toggle
- ✅ Web channel (onboarding): both entry paths work — blank-write (fill the 5 persona blocks by hand) and upload-material (paste a bio/journal, LLM drafts the 5 blocks for your review)
- 🚧 Web channel (placeholders this release): admin → events list / thoughts list / voice cloning wizard / config tabs exist but most render section chrome only; a few are fully wired (Persona blocks · Voice toggle · Memory search · Cost breakdown · etc. — see CHANGELOG for the exact map)
- ✅ Discord channel: DM ingestion with debounce · text replies · native OGG Opus voice messages (ffmpeg required)
- ✅ Import pipeline (library only): universal LLM importer · five content-type classification ·
self_blockside path · mandatory embed pass — no HTTP route is exposed yet, so neither the Web SPA nor the CLI can drive a real import in this release ⚠️ Platform: macOS and Linux tested; Windows is not yet supported- 🔜 v0.0.2 targets: wire
/api/admin/import/*routes + Web import wizard · Admin events / thoughts list views · live mood / session-boundary SSE feed on the Web chat
Full module-by-module documentation lives under docs/ (English + 中文). Start at the landing page in your language and follow the cross-links:
- 🇬🇧 docs/en/README.md · 🇨🇳 docs/zh/README.md
The module pages cover memory, voice, channels, proactive, runtime, and import, plus configuration and contributing. The three HTML visualizations above are the fastest way to see the system on one page.
EchoVessel means carrying an echo long enough for it to become presence.
