chore: initialize EverOS 1.0.0

md-first memory extraction framework for AI agents.

Markdown is the single source of truth; SQLite holds state and LanceDB
provides the rebuildable vector + BM25 + scalar index. The codebase follows
a single-direction DDD layering (entrypoints -> service -> memory -> infra,
with component / core / config cross-cutting) enforced by import-linter.

Engineering surface:
- Coding conventions in .claude/rules/ (path-scoped) and workflows in
  .claude/skills/ (/commit, /new-branch, /pr).
- GitHub Actions CI runs make lint + test + integration; pre-commit mirrors
  the gates locally (ruff, hygiene hooks, gitlint commit-msg).
- Commit messages follow Conventional Commits, enforced by gitlint.
- make lint also enforces datetime two-zone discipline and OpenAPI drift.
This commit is contained in:
Elliot Chen
2026-06-05 22:35:51 +08:00
commit 518b8eca85
636 changed files with 160553 additions and 0 deletions

View File

@ -0,0 +1,66 @@
"""
Neural network context features — showing how EverCore expands
the persona engine's perception from 8D to 12D.
The 4 additional relationship dimensions from EverCore allow the
neural network to produce different behavioral signals depending
on the history between user and persona.
Full source: https://github.com/kellyvv/OpenHer/blob/main/engine/genome/genome_engine.py
"""
# ══════════════════════════════════════════════
# 5D Drive System (internal motivation)
# ══════════════════════════════════════════════
DRIVES = ['connection', 'novelty', 'expression', 'safety', 'play']
# ══════════════════════════════════════════════
# 8D Behavioral Signals (neural network output)
# ══════════════════════════════════════════════
SIGNALS = [
'directness', # 0=indirect hints → 1=straight talk
'vulnerability', # 0=guarded → 1=emotionally open
'playfulness', # 0=serious → 1=playful/teasing
'initiative', # 0=reactive → 1=proactive leading
'depth', # 0=small talk → 1=deep conversation
'warmth', # 0=cold/distant → 1=warm/caring
'defiance', # 0=compliant → 1=rebellious/stubborn
'curiosity', # 0=indifferent → 1=intensely curious
]
# ══════════════════════════════════════════════
# 12D Context Features (neural network input)
# ══════════════════════════════════════════════
CONTEXT_FEATURES = [
# ── 8D from Critic LLM (per-turn perception) ──
'user_emotion', # -1=negative → 1=positive
'topic_intimacy', # 0=professional → 1=intimate
'time_of_day', # 0=morning → 1=late night
'conversation_depth', # 0=just started → 1=deep conversation
'user_engagement', # 0=dismissive → 1=invested
'conflict_level', # 0=harmonious → 1=conflict
'novelty_level', # 0=routine topic → 1=novel topic
'user_vulnerability', # 0=guarded → 1=open
# ── 4D from EverCore (cross-session relationship) ──
'relationship_depth', # 0=stranger → 1=old friend
'emotional_valence', # -1=negative history → 1=positive history
'trust_level', # 0=no trust → 1=deep trust
'pending_foresight', # 0=nothing pending → 1=unresolved concern
]
# Neural network dimensions
N_DRIVES = len(DRIVES) # 5
N_CONTEXT = len(CONTEXT_FEATURES) # 12 (8 + 4 from EverCore)
N_SIGNALS = len(SIGNALS) # 8
RECURRENT_SIZE = 8 # Internal "mood" state
INPUT_SIZE = N_DRIVES + N_CONTEXT + RECURRENT_SIZE # 5 + 12 + 8 = 25
HIDDEN_SIZE = 24
# Architecture: 25D input → 24D hidden (tanh) → 8D output (sigmoid)
# The 4 EverCore dimensions mean the same neural network produces
# DIFFERENT behavioral signals for strangers vs. old friends,
# even with identical conversation context.

View File

@ -0,0 +1,193 @@
"""
EverMemosMixin — EverCore integration for ChatAgent.
This mixin handles all async memory operations in the ChatAgent lifecycle:
Step 0: Session context loading (first turn)
Step 2.5: Relationship EMA (blend EverCore prior + LLM delta)
Step 8.5: Collect async search results
Step 11: Fire-and-forget turn storage
Step 12: Async prefetch for next turn
The mixin pattern keeps EverCore concerns cleanly separated from the
core persona engine (drives, metabolism, neural network, style memory).
Full source: https://github.com/kellyvv/OpenHer/blob/main/agent/evermemos_mixin.py
"""
from __future__ import annotations
import asyncio
class EverMemosMixin:
"""EverCore async memory integration methods."""
async def _evermemos_gather(self) -> dict:
"""
Step 0: Load EverCore session context (first turn only).
Subsequent turns reuse cached _session_ctx.
Returns relationship_4d dict for GenomeEngine context.
"""
empty_4d = {
'relationship_depth': 0.0,
'emotional_valence': 0.0,
'trust_level': 0.0,
'pending_foresight': 0.0,
}
if not (self.evermemos and self.evermemos.available):
return empty_4d
# Load once per session
if self._turn_count == 1:
self._session_ctx = await self.evermemos.load_session_context(
user_id=self.evermemos_uid,
persona_id=self.persona.persona_id,
group_id=self._group_id,
)
if self._session_ctx.user_profile:
self._user_profile = self._session_ctx.user_profile
if self._session_ctx.episode_summary:
self._episode_summary = self._session_ctx.episode_summary
# Cache foresight text for Actor injection
if self._session_ctx.foresight_text:
self._foresight_text = self._session_ctx.foresight_text
if not self._session_ctx:
return empty_4d
return self.evermemos.relationship_vector(self._session_ctx)
def _apply_relationship_ema(
self,
prior: dict,
rel_delta: dict,
conversation_depth: float,
) -> dict:
"""
Step 2.5: Semi-emergent relationship update.
Pattern: posterior = clip(prior + LLM_delta) → EMA smooth
alpha = clip(0.15 + 0.5 * depth, 0.15, 0.65)
state_t = alpha * posterior + (1 - alpha) * state_{t-1}
First turn initializes EMA state from prior, then applies delta normally.
"""
# Map Critic output keys → context feature keys
delta_map = {
'relationship_depth': rel_delta.get('relationship_delta', 0.0),
'emotional_valence': rel_delta.get('emotional_valence', 0.0),
'trust_level': rel_delta.get('trust_delta', 0.0),
'pending_foresight': 0.0, # No delta for foresight (data-driven only)
}
# Initialize EMA on first turn
if not self._relationship_ema:
self._relationship_ema = dict(prior)
# Compute posterior = clip(prior + delta)
posterior = {}
for k in prior:
lo = -1.0 if k == 'emotional_valence' else 0.0
posterior[k] = max(lo, min(1.0, prior[k] + delta_map.get(k, 0.0)))
# Depth-modulated alpha: shallow → trust prior, deep → trust LLM
alpha = max(0.15, min(0.65, 0.15 + 0.5 * conversation_depth))
# EMA smooth
ema = {}
for k in prior:
prev = self._relationship_ema.get(k, prior[k])
ema[k] = round(alpha * posterior[k] + (1 - alpha) * prev, 4)
self._relationship_ema = ema
return ema
def _evermemos_store_bg(self, user_message: str, reply: str) -> None:
"""Step 11: Fire-and-forget EverCore storage (asyncio.create_task)."""
if not (self.evermemos and self.evermemos.available):
return
async def _do_store():
try:
await self.evermemos.store_turn(
user_id=self.evermemos_uid,
persona_id=self.persona.persona_id,
persona_name=self.persona.name,
user_name=self.user_name or "用户",
group_id=self._group_id,
user_message=user_message,
agent_reply=reply,
)
except Exception as e:
print(f" [evermemos] ❌ store failed: {type(e).__name__}: {e}")
try:
asyncio.create_task(_do_store())
except Exception as e:
print(f" [evermemos] create_task error: {e}")
def _evermemos_search_bg(self, user_message: str) -> None:
"""
Step 12: Fire async RRF search for the current user_message.
Results are collected at Step 8.5 of the NEXT turn.
Cancels any pending search before starting a new one.
"""
if not (self.evermemos and self.evermemos.available):
return
if not self._session_ctx or not self._session_ctx.has_history:
return
# Cancel any orphaned previous search task
if self._search_task and not self._search_task.done():
self._search_task.cancel()
self._search_task = None
try:
self._search_turn_id = self._turn_count
self._search_task = asyncio.create_task(
self.evermemos.search_relevant_memories(
query=user_message,
user_id=self.evermemos_uid,
group_id=self._group_id,
)
)
except Exception as e:
print(f" [evermemos] search create_task error: {e}")
self._search_task = None
async def _collect_search_results(self) -> None:
"""
Collect previous turn's async search results (called at Step 8.5).
Validates turn_id to prevent concurrent mismatch.
Waits up to 0.5s; on timeout/error falls back to empty.
"""
if self._search_task is None:
return
# Concurrency guard: reject stale results from wrong turn
expected_turn = self._turn_count - 1
if self._search_turn_id != expected_turn:
self._search_task.cancel()
self._search_task = None
self._relevant_facts = ""
self._relevant_episodes = ""
self._relevant_profile = ""
return
try:
facts, episodes, profile = await asyncio.wait_for(
self._search_task, timeout=0.5
)
self._relevant_facts = facts
self._relevant_episodes = episodes
self._relevant_profile = profile
except asyncio.TimeoutError:
# Graceful degradation: use static session context
self._relevant_facts = ""
self._relevant_episodes = ""
self._relevant_profile = ""
except Exception:
self._relevant_facts = ""
self._relevant_episodes = ""
self._relevant_profile = ""
finally:
self._search_task = None

View File

@ -0,0 +1,66 @@
"""
Memory shared types for OpenHer.
These types bridge the two memory providers:
- SoulMem (behavioral memory, always-on SQLite layer)
- EverCore (declarative memory, cross-session persistence)
The SessionContext is the key data structure loaded from EverCore
at session start — it provides relationship priors, user profile,
episode summaries, and foresight data that expand the neural
network's perception from 8D to 12D.
Full source: https://github.com/kellyvv/OpenHer/blob/main/memory/types.py
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
@dataclass
class Memory:
"""A single memory entry (SoulMem behavioral layer)."""
memory_id: int = 0
user_id: str = ""
persona_id: str = ""
content: str = ""
category: str = "conversation" # conversation | fact | event | preference
importance: float = 0.5
source_turn: int = 0
created_at: float = 0.0
@dataclass
class SessionContext:
"""
EverCore session context (declarative memory).
Loaded once at session start, this contains everything the
persona needs to know about the user from past sessions:
- user_profile: Who they are (name, preferences, occupation)
- episode_summary: What happened between us (narrative history)
- foresight_text: What we should pay attention to (unresolved topics)
- relationship_*: 4D vector feeding the neural network
- has_history: Whether there's prior interaction (gates search)
These values feed into the ChatAgent lifecycle at multiple steps:
- Step 0: Session context loaded
- Step 2: user_profile + episode_summary inject into Critic prompt
- Step 2.5: relationship_* feed EMA computation
- Step 5: 4D vector enters neural network as context features
- Step 8.5: Used as fallback when async search times out
"""
user_id: str = ""
persona_id: str = ""
user_profile: str = ""
episode_summary: str = ""
foresight_text: str = ""
relationship_depth: float = 0.0
emotional_valence: float = 0.0
trust_level: float = 0.0
pending_foresight: float = 0.0
has_history: bool = False
raw_data: Optional[dict] = None