Theo’s personalization is layered from:
- Base assistant/session instructions.
- Active user profile context.
- Memory hydration (tiny startup digest + turn-time retrieval).
- Assistant name defaults to
Theo. - Active user defaults to
default. - Empty profiles are created lazily with unknown/missing fields.
Via update_user_profile:
namepreferencesfavorites
Stored in var/user_profiles.db.
Durable memory is stored in var/memories.db.
Theo ingests memories via two explicit paths:
- Manual tool call (
remember_memory)source=manual_tool- optional manual pinning (
pinned=true)
- Response-done auto-memory (reflection)
source=auto_reflection- config-gated by:
memory.auto_memory_enabledmemory.require_confirmation_for_auto_memorymemory.auto_memory_min_confidence
Memory scope is explicit:
user_global: available across runs for active user.session_local: isolated to active runtimesession_id.
Theo assigns runtime session_id=run-<run_id> at startup, so session-local behavior is stable.
Theo uses a two-tier hydration model to avoid empty context and avoid full preload:
At session initialization, Theo injects a small pinned digest:
- sourced from pinned, review-approved user-global memories,
- capped by
memory_hydration.startup_digest_max_itemsandstartup_digest_max_chars.
Typical preload examples:
- core identity/favorite name pronunciation,
- persistent preference like "prefers concise answers",
- stable safety preference.
On each user turn, Theo retrieves only highly relevant memories:
- lexical/tag relevance + importance + recency ranking,
- stale suppression and near-duplicate suppression,
- strict caps (
memory_retrieval.max_memories,memory_retrieval.max_chars), - anti-bloat skip for short/noisy user inputs,
- cooldown between retrieval injections.
Typical turn retrieval examples:
- user asks about coffee setup → recall brewing preference,
- user asks project status → recall project-specific memory,
- short/noisy input (“ok”, “thanks”) → skip retrieval.
Memories support pinning metadata:
pinnedmarks items eligible for startup digest.needs_reviewkeeps auto-pinned items out of startup preload until reviewed.
Auto-reflection memories can be auto-pinned at high importance using:
memory.auto_pin_min_importancememory.auto_pin_requires_review
Each memory row stores:
- scope identifiers (
user_id, optionalsession_id), source(manual_tool/auto_reflection),- pin/review flags (
pinned,needs_review).
forget_memory(memory_id=...) deletes a memory row by id.
The semantic memory rollout can be disabled explicitly with config only:
memory_semantic:
enabled: false
rerank_enabled: false- Set both keys together so semantic retrieval and reranking are unambiguously off.
- No DB rollback is required when disabling this feature.
- Existing
memoriesandmemory_embeddingsrows can remain in place; the runtime will continue using lexical retrieval paths.
By default, memory_semantic.background_embedding_enabled: true enables the background queue worker mode from config/default.yaml.
- Background queue worker mode (default): writes stay on the lexical path and enqueue embedding work for background processing.
- Inline write-time embedding mode (opt-in): set
background_embedding_enabled: falseandinline_embedding_on_write_when_background_disabled: trueto synchronously attempt embeddings during writes.
background_embedding_enabled |
inline_embedding_on_write_when_background_disabled |
Effective behavior |
|---|---|---|
true (default) |
false (default) |
Background queue worker generates embeddings asynchronously. |
false |
true |
Inline write-time embedding attempts run synchronously. |
false |
false |
Lexical-only writes; embeddings are not attempted at write time. |
To opt into synchronous write-time embeddings, set:
memory_semantic:
enabled: true
background_embedding_enabled: false
inline_embedding_on_write_when_background_disabled: trueThis path is fail-open: memory rows are still written even if embedding calls time out or fail, and embedding status is recorded as pending/error for observability.
Tradeoff: inline embedding increases per-write CPU and can add write latency on constrained hardware, so the flag remains opt-in.
At startup, semantic memory now runs a bounded embedding canary (text="ping", query path) before marking semantic reranking provider-ready.
- If the canary succeeds, semantic reranking can proceed.
- If it fails, semantic reranking stays disabled and reports a readiness reason like
canary_timeout,canary_auth, orcanary_connection.
Configuration knobs:
memory_semantic:
startup_canary_timeout_ms: 120
startup_canary_bypass: falseOperational/testing override:
- Set
PYPIBOT_SEMANTIC_CANARY_BYPASS=1to bypass canary gating explicitly (useful for offline test rigs). - Keep this unset in production unless you intentionally want semantic reranking to ignore startup canary health.