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)user_block — identity-level facts about youstyle_block — owner-curated style preferences (don't say "haha", etc.)recall_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 IN (THOUGHT, INTENTION, EXPECTATION) link · concept_node_fillingtype=thought · subject='user' — durable insight about the user ("you find quiet afternoons grounding")type=thought · subject='persona' — persona's self-reflection produced by slow_tick · feeds the user prompt's # How you see yourself lately section · the only physical path by which the persona's self-image growstype=intention — strict commitment with subject='persona' ("I'll text you back tomorrow")type=expectation — slow_tick forward prediction with event_time_end as due_atconcept_node_filling · parent=thought, child=event · the evidence chainreflect_fn (fast loop · SHOCK / TIMER) · slow_tick G phase (slow loop · between-session reflection · also where subject='persona' rows come from)# About {speaker} · # How you see yourself lately · # Promises you've made · # You've been expecting · delete → shows source events to confirm cascadeentities entity_aliases junction · concept_node_entitiesentities · canonical name + kind (person | place | org | pet) + tri-state merge_status + a description prose columnentity_aliases · many-to-one alias → entity (case-sensitive, CJK kept whole)concept_node_entities · many-to-many junction binding L3 events to L5 entitiesentity_anchor rerank bonusdescription · slow_tick auto-synthesizes when linked_events_count ≥ 3 · owner can PATCH /api/admin/memory/entities/{id} to override (sets owner_override=true) · renders into the system prompt as # About {canonical_name} on alias hitsdescription via update_entity_description(...)find_query_entities() alias scan · # About {canonical_name} rendered when description is non-empty · # Entity disambiguation pending hint when uncertainpersonas.episodic_state JSON{mood, energy, last_user_signal, updated_at}session_mood_signal alongside events; consolidate writes it through · zero extra LLM callsassemble_turn entry decays mood back to neutral if the snapshot is older than 12 hours# How you feel right now in the system prompt; section is skipped while mood is neutralsession_mood_signalconcept_nodes + entities with embeddings via sqlite-vec → side-effect updates personas.episodic_state from extraction's session_mood_signal → optional G phase runs slow_cycle for between-session reflection (cool-down + token-wall + daily-cap gates). Four tuning knobs (trivial_message_count, trivial_token_count, reflection_hard_gate_24h, memory.relational_bonus_weight) are live via config.toml; slow_tick has its own [slow_tick] section.
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.