uv run lint-imports fails CI if any module imports upward. The second contract (proactive must not import runtime or prompts) prevents a subtle cycle where proactive scheduling would transitively pull in the LLM factory.
core_blocks audit · core_block_appendspersona_block — who this persona is (trait, tone, values)self_block — persona's self-narrative (grows by reflection)user_block — identity-level facts about yourelationship_block — people in your life as persona sees themmood_block — transient · auto-refreshed each turnrecall_messages fts · recall_messages_ftsrole: "user" | "persona"channel_id stored, but never read as filter (D4)session_id groups a conversation burstturn_id groups one user→persona exchangememory.ingest_message() · per message · atomic/api/chat/history · recent-window context · admin memory searchconcept_nodes WHERE type=EVENT vec · concept_nodes_vecdescription — short 1-2 sentence summaryemotional_impact — signed integer, drives retrieval weightingemotion_tags / relational_tags — JSON listssource_session_id, imported_from)concept_nodes WHERE type=THOUGHT link · concept_node_fillingdescription — one durable insight ("you find quiet afternoons grounding")concept_node_filling · parent=thought, child=event · the evidence chainreflect_fn · LLM callconcept_nodes with embeddings via sqlite-vec. Four tuning knobs (trivial_message_count, trivial_token_count, reflection_hard_gate_24h, memory.relational_bonus_weight) are live via config.toml.
runtime.interaction.assemble_turn() performs 5 sub-steps per turn — ingest each message into L2, run retrieve() (no channel_id filter!), assemble prompt from core_blocks + retrieved L3/L4, call llm.complete() with streaming tokens, and ingest the assistant's reply into L2 once streaming finishes.
source_channel_id. Web UI renders a channel pill (📱 Discord / 💬 iMessage). Failure-isolated: if publish raises, the originating channel's send() still succeeds.
useChat calls getChatHistory(50), reverses to ascending, prepends into the timeline, and only then starts the SSE stream. Cursor paging via before=<turn_id> walks further back. "Load older" button prepends more.
| Param | Semantics |
|---|---|
limit | 1–200, default 50 · clamped 422 on overflow |
before | turn_id cursor · returns messages older than that turn's first message · 404 if cursor unknown |
127.0.0.1:7777~/.echovessel/config.tomlechovessel init from resources/config.toml.sample. Sections: [runtime], [persona], [memory], [llm], [consolidate], [idle_scanner], [voice], [proactive], [channels.web], [channels.discord]. Hot-reload set: LLM provider/model/params, persona display_name, memory tuning, consolidate thresholds. Restart-required set: data_dir, db_path.
./.env (CWD)_load_dotenv() at echovessel run startup from Path.cwd() / ".env". Shell-exported env vars take precedence. echovessel init writes a commented-out template at chmod 0600 and never overwrites an existing .env (not even with --force). The committed template is .env.example.
retrieve(), search, consolidate — none of them take a channel_id filter. The persona is one identity; memory is one store. Discord and Web share everything.send() still succeeds. Observability never breaks delivery.| Commit | Scope |
|---|---|
4357250 | Initial snapshot — EchoVessel pre-v0.0.1 |
7325498 | Round 1 truth-layer landing — 4 config fields wired through, runtime/proactive.py dead stub removed, SSE pruning |
ed7fe4a | Round 2 · Import pipeline wired end-to-end — facade + 5 admin routes + 3-step wizard |
1959fe3 | Wave A · Admin UI truth-layer — Events/Thoughts/forget/mood/session boundary/Discord status |
8006185 | Wave B · Cost tracking + Config edit |
36fe8b8 | Wave C · Memory search / trace / onboarding path 2 / voice clone |
85e39f9 | Housekeeping · SQLite lock fix · FastAPI 422 rename |
d9c6ba3 | CI fix · skip eval tests when corpus missing · loosen facade timeout |
f67bc07 | echovessel init writes .env template |
49bf995 | Move .env to repo-root (CWD) + add .env.example |
3535406 | Scrub remaining ~/.echovessel/.env refs in docs |
6e1d3af | README/CHANGELOG · drop PyPI-install framing (not released yet) |
54f69d2 | Cross-channel unified Web timeline (live SSE + history backfill) |
6e3a7a1 | Fix cost_logger table creation + CHANGELOG truth sweep |
a8fc089 | Public docs · cross-channel + truth sweep |
852fb62 | docs/channels: naming note (channel == stateful message gateway) |
docs/architecture.html in the EchoVessel repo. Linked from docs/README.md and both language landing pages. Re-generate by editing this file directly — it's hand-written HTML, not compiled from markdown.