chore: finalize repo audit hygiene (#257)

This commit is contained in:
Elliot Chen
2026-06-06 13:59:12 +08:00
committed by GitHub
parent ab23e40b28
commit 00f1dfaec5
27 changed files with 459 additions and 199 deletions

View File

@ -11,7 +11,7 @@ body:
attributes: attributes:
label: Area label: Area
options: options:
- methods/EverCore - src/everos
- methods/HyperMem - methods/HyperMem
- benchmarks/EverMemBench - benchmarks/EverMemBench
- benchmarks/EvoAgentBench - benchmarks/EvoAgentBench

View File

@ -7,7 +7,7 @@ body:
id: page id: page
attributes: attributes:
label: Page or file label: Page or file
placeholder: README.md, methods/EverCore/docs/... placeholder: README.md, docs/architecture.md, use-cases/...
validations: validations:
required: true required: true
- type: textarea - type: textarea

View File

@ -35,6 +35,11 @@ repos:
entry: python3 scripts/check_repo_assets.py entry: python3 scripts/check_repo_assets.py
language: system language: system
pass_filenames: false pass_filenames: false
- id: no-deprecated-product-names
name: block deprecated product names
entry: python3 scripts/check_deprecated_names.py
language: system
pass_filenames: false
- repo: https://github.com/jorisroovers/gitlint - repo: https://github.com/jorisroovers/gitlint
rev: v0.19.1 rev: v0.19.1

View File

@ -1,13 +1,14 @@
.PHONY: help install install-deps lint docs-check check-commits check-assets check-cjk check-datetime openapi check-openapi format test integration package cov ci clean .PHONY: help install install-deps lint docs-check check-commits check-assets check-deprecated-names check-cjk check-datetime openapi check-openapi format test integration package cov ci clean
help: help:
@echo "Targets:" @echo "Targets:"
@echo " install Install deps + pre-commit hooks (full dev setup)" @echo " install Install deps + pre-commit hooks (full dev setup)"
@echo " install-deps Install deps only (uv sync --frozen, used by CI)" @echo " install-deps Install deps only (uv sync --frozen, used by CI)"
@echo " lint ruff + import-linter + repo asset/media + datetime discipline + openapi drift" @echo " lint ruff + import-linter + repo hygiene + datetime discipline + openapi drift"
@echo " docs-check Validate Markdown links, use-case banners, and issue template YAML" @echo " docs-check Validate Markdown links, use-case banners, and issue template YAML"
@echo " check-commits Validate Conventional Commit subjects for a git range" @echo " check-commits Validate Conventional Commit subjects for a git range"
@echo " check-assets Block committed images, videos, and asset/media directories" @echo " check-assets Block committed images, videos, and asset/media directories"
@echo " check-deprecated-names Block deprecated product names"
@echo " check-cjk Scan for CJK outside the language-policy allowlist (advisory)" @echo " check-cjk Scan for CJK outside the language-policy allowlist (advisory)"
@echo " check-datetime Scan for code that bypasses component/utils/datetime (HARD gate, run via lint)" @echo " check-datetime Scan for code that bypasses component/utils/datetime (HARD gate, run via lint)"
@echo " openapi Regenerate docs/openapi.json from the FastAPI app" @echo " openapi Regenerate docs/openapi.json from the FastAPI app"
@ -36,6 +37,7 @@ lint:
uv run ruff format --check src tests uv run ruff format --check src tests
uv run lint-imports uv run lint-imports
uv run python scripts/check_repo_assets.py uv run python scripts/check_repo_assets.py
uv run python scripts/check_deprecated_names.py
uv run python scripts/check_datetime_discipline.py uv run python scripts/check_datetime_discipline.py
uv run python scripts/dump_openapi.py --check uv run python scripts/dump_openapi.py --check
@ -51,6 +53,10 @@ check-commits:
check-assets: check-assets:
uv run python scripts/check_repo_assets.py uv run python scripts/check_repo_assets.py
# Product naming gate. Public repo text should use EverOS or EverMind Cloud.
check-deprecated-names:
uv run python scripts/check_deprecated_names.py
# Advisory CJK scan (see .claude/rules/language-policy.md). Deliberately NOT # Advisory CJK scan (see .claude/rules/language-policy.md). Deliberately NOT
# wired into `lint` / `ci`: the policy is enforced by review and the rules # wired into `lint` / `ci`: the policy is enforced by review and the rules
# doc, not a hard gate. Run on demand when touching potentially-CJK files. # doc, not a hard gate. Run on demand when touching potentially-CJK files.

View File

@ -125,11 +125,11 @@ read the markdown), see [QUICKSTART.md](QUICKSTART.md).
### Develop locally ### Develop locally
```bash ```bash
git clone <repo> git clone https://github.com/EverMind-AI/EverOS.git
cd everos cd EverOS
uv sync # creates ./.venv and installs deps uv sync # creates ./.venv and installs deps
source .venv/bin/activate # — or skip activation and prefix every command with `uv run` source .venv/bin/activate # — or skip activation and prefix every command with `uv run`
everos init # fill in EVEROS_LLM__API_KEY in the generated .env everos init # fill the four API key slots in .env (two distinct keys)
everos --help everos --help
make test make test

View File

@ -204,7 +204,7 @@ everalgo is:
- **No I/O** — does not touch md files / LanceDB / SQLite - **No I/O** — does not touch md files / LanceDB / SQLite
- **No prompts inline** — receives `PromptSlot` parameter, project supplies defaults - **No prompts inline** — receives `PromptSlot` parameter, project supplies defaults
This boundary lets everalgo be reused across product forms (this open-source build, EverOS Cloud, OpenClaw plugins, etc.). This boundary lets everalgo be reused across product forms (this open-source build, EverMind Cloud, OpenClaw plugins, etc.).
## Further reading ## Further reading

View File

@ -21,9 +21,7 @@ ALLOWED_TYPES = (
"ci", "ci",
"revert", "revert",
) )
TITLE_RE = re.compile( TITLE_RE = re.compile(rf"^({'|'.join(ALLOWED_TYPES)})(\([A-Za-z0-9._/-]+\))?(!)?: .+")
rf"^({'|'.join(ALLOWED_TYPES)})(\([A-Za-z0-9._/-]+\))?(!)?: .+"
)
MAX_TITLE_LENGTH = 72 MAX_TITLE_LENGTH = 72
@ -33,7 +31,9 @@ def _run_git(args: list[str]) -> str:
def _default_range() -> str: def _default_range() -> str:
event_name = os.getenv("GITHUB_EVENT_NAME", "") event_name = os.getenv("GITHUB_EVENT_NAME", "")
before = os.getenv("GITHUB_EVENT_BEFORE", "") or os.getenv("GITHUB_EVENT_BEFORE_SHA", "") before = os.getenv("GITHUB_EVENT_BEFORE", "") or os.getenv(
"GITHUB_EVENT_BEFORE_SHA", ""
)
after = os.getenv("GITHUB_SHA", "HEAD") after = os.getenv("GITHUB_SHA", "HEAD")
pr_base = os.getenv("GITHUB_PR_BASE_SHA", "") pr_base = os.getenv("GITHUB_PR_BASE_SHA", "")
@ -83,7 +83,8 @@ def _validate(commit_range: str) -> list[str]:
short = commit[:12] short = commit[:12]
if len(subject) > MAX_TITLE_LENGTH: if len(subject) > MAX_TITLE_LENGTH:
failures.append( failures.append(
f"{short}: subject is {len(subject)} chars; max is {MAX_TITLE_LENGTH}: {subject}" f"{short}: subject is {len(subject)} chars; "
f"max is {MAX_TITLE_LENGTH}: {subject}"
) )
continue continue

View File

@ -0,0 +1,90 @@
"""Block deprecated product names in tracked repository text."""
from __future__ import annotations
import re
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable
DEPRECATED_NAME_RE = re.compile(r"\bever[\s_-]*core\b", flags=re.IGNORECASE)
SKIP_SUFFIXES = frozenset(
{
".avif",
".bmp",
".gif",
".heic",
".heif",
".icns",
".ico",
".jpeg",
".jpg",
".mov",
".mp4",
".png",
".webp",
}
)
@dataclass(frozen=True)
class Violation:
path: str
line_number: int
line: str
def find_violations(files: Iterable[tuple[str, str]]) -> list[Violation]:
violations: list[Violation] = []
for path, text in files:
for line_number, line in enumerate(text.splitlines(), start=1):
if DEPRECATED_NAME_RE.search(line):
violations.append(
Violation(path=path, line_number=line_number, line=line.strip())
)
return violations
def _tracked_paths() -> list[Path]:
result = subprocess.run(
["git", "ls-files", "-z"],
check=True,
stdout=subprocess.PIPE,
text=False,
)
return [
Path(raw.decode("utf-8"))
for raw in result.stdout.split(b"\0")
if raw
]
def _tracked_text_files() -> Iterable[tuple[str, str]]:
for path in _tracked_paths():
if path.suffix.lower() in SKIP_SUFFIXES:
continue
try:
text = path.read_text(encoding="utf-8")
except UnicodeDecodeError:
continue
yield path.as_posix(), text
def main() -> int:
violations = find_violations(_tracked_text_files())
if not violations:
print("Deprecated-name check passed.")
return 0
print(
"Deprecated-name check failed.\n"
"Use EverOS or EverMind Cloud. Do not use deprecated product naming.\n"
)
for violation in violations:
print(f"- {violation.path}:{violation.line_number}: {violation.line}")
return 1
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -3,7 +3,6 @@
from __future__ import annotations from __future__ import annotations
import re import re
import sys
from pathlib import Path from pathlib import Path
SKIP_DIRS = {".git", "node_modules", ".venv", ".uv-cache"} SKIP_DIRS = {".git", "node_modules", ".venv", ".uv-cache"}

View File

@ -3,10 +3,9 @@
from __future__ import annotations from __future__ import annotations
import subprocess import subprocess
import sys from collections.abc import Iterable
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import PurePosixPath from pathlib import PurePosixPath
from typing import Iterable
BLOCKED_DIR_NAMES = frozenset( BLOCKED_DIR_NAMES = frozenset(
{ {
@ -94,11 +93,7 @@ def _tracked_paths() -> list[str]:
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
text=False, text=False,
) )
return [ return [raw.decode("utf-8") for raw in result.stdout.split(b"\0") if raw]
raw.decode("utf-8")
for raw in result.stdout.split(b"\0")
if raw
]
def main() -> int: def main() -> int:

View File

@ -39,7 +39,7 @@ from .protocol import RerankError, RerankResult
# Qwen3-Reranker chat template. The DeepInfra inference API treats the reranker # Qwen3-Reranker chat template. The DeepInfra inference API treats the reranker
# as a yes/no generator, so the prompt scaffolding must be supplied client-side # as a yes/no generator, so the prompt scaffolding must be supplied client-side
# (verbatim mirror of the EverCore benchmark's reranker client). Without it the # (verbatim mirror of the benchmark reranker client). Without it the
# model scores raw text off-template and returns uncalibrated relevance. # model scores raw text off-template and returns uncalibrated relevance.
_QWEN3_PREFIX = ( _QWEN3_PREFIX = (
"<|im_start|>system\n" "<|im_start|>system\n"

View File

@ -180,4 +180,6 @@ def register(parent: typer.Typer) -> None:
typer.echo("Next steps:") typer.echo("Next steps:")
typer.echo(" 1. Edit the file and fill in the API keys (see comments inside).") typer.echo(" 1. Edit the file and fill in the API keys (see comments inside).")
typer.echo(" 2. Run `everos server start`.") typer.echo(" 2. Run `everos server start`.")
typer.echo("Docs: https://github.com/evermind/everos/blob/master/QUICKSTART.md") typer.echo(
"Docs: https://github.com/EverMind-AI/EverOS/blob/main/QUICKSTART.md"
)

View File

@ -518,7 +518,7 @@ class SearchManager:
``atomic_fact`` table) for finer-grained semantic match — long ``atomic_fact`` table) for finer-grained semantic match — long
episodes whose single mean-pooled vector dilutes a specific topic episodes whose single mean-pooled vector dilutes a specific topic
recover via the matching atomic fact's own embedding. Mirrors recover via the matching atomic fact's own embedding. Mirrors
EverOS/EverCore's MaxSim retrieval pattern. the EverOS MaxSim retrieval pattern.
""" """
vector = await self._embed_query(req.query) vector = await self._embed_query(req.query)
if not vector: if not vector:

View File

@ -41,6 +41,9 @@ def test_default_writes_dotenv_in_cwd(runner: CliRunner, in_tmp: Path) -> None:
assert written.exists() assert written.exists()
assert written.stat().st_size > 0 assert written.stat().st_size > 0
assert "EVEROS_LLM__API_KEY" in written.read_text() assert "EVEROS_LLM__API_KEY" in written.read_text()
assert "https://github.com/EverMind-AI/EverOS/blob/main/QUICKSTART.md" in (
result.output
)
def test_default_file_permissions_are_0600(runner: CliRunner, in_tmp: Path) -> None: def test_default_file_permissions_are_0600(runner: CliRunner, in_tmp: Path) -> None:

View File

@ -163,7 +163,7 @@ async def test_merges_into_existing_cluster_when_algo_matches() -> None:
preview=["earlier intent"], preview=["earlier intent"],
members=["ac_20260517_0000"], members=["ac_20260517_0000"],
) )
# Simulate evercore _merge: id passes through from existing, members appended. # Simulate merge behavior: id passes through from existing, members appended.
merged_cluster = AlgoCluster( merged_cluster = AlgoCluster(
id="cl_existing0001", id="cl_existing0001",
centroid=np.array([0.17] * 1024, dtype=np.float32), centroid=np.array([0.17] * 1024, dtype=np.float32),

View File

@ -0,0 +1,59 @@
"""Self-tests for ``scripts/check_deprecated_names.py``."""
from __future__ import annotations
import importlib.util
import sys
from pathlib import Path
_REPO_ROOT = Path(__file__).resolve().parents[3]
_CHECKER_PATH = _REPO_ROOT / "scripts" / "check_deprecated_names.py"
def _load_checker():
assert _CHECKER_PATH.exists(), "deprecated-name checker should exist"
spec = importlib.util.spec_from_file_location(
"_deprecated_name_checker", _CHECKER_PATH
)
assert spec and spec.loader
mod = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = mod
spec.loader.exec_module(mod)
return mod
def test_clean_text_is_allowed() -> None:
checker = _load_checker()
violations = checker.find_violations(
[("README.md", "EverOS is the public project name.\n")]
)
assert violations == []
def test_deprecated_name_variants_are_blocked() -> None:
checker = _load_checker()
compact_name = "Ever" + "Core"
spaced_name = "ever" + " core"
hyphenated_name = "ever" + "-core"
violations = checker.find_violations(
[
("README.md", f"{compact_name} should not appear.\n"),
("docs/example.md", f"{spaced_name} should not appear.\n"),
("src/example.py", f"{hyphenated_name} should not appear.\n"),
]
)
assert [(violation.path, violation.line_number) for violation in violations] == [
("README.md", 1),
("docs/example.md", 1),
("src/example.py", 1),
]
def test_real_repo_has_no_deprecated_names() -> None:
checker = _load_checker()
assert checker.main() == 0

View File

@ -1,6 +1,6 @@
# EverMem Story Memory Demo # EverMem Story Memory Demo
> Built on [EverCore](https://github.com/EverMind-AI/EverOS/) - Open-source AI memory infrastructure > Built on [EverOS](https://github.com/EverMind-AI/EverOS/) - Open-source AI memory infrastructure
A demonstration web application showcasing [EverMem](https://evermind.ai)'s AI memory infrastructure through an interactive Q&A experience with "A Game of Thrones" (Book 1). A demonstration web application showcasing [EverMem](https://evermind.ai)'s AI memory infrastructure through an interactive Q&A experience with "A Game of Thrones" (Book 1).
@ -30,7 +30,7 @@ Ask questions about the book and watch two AI responses stream side-by-side: one
- [Bun](https://bun.sh/) (latest version) - [Bun](https://bun.sh/) (latest version)
- OpenAI API key (or OpenRouter API key) - OpenAI API key (or OpenRouter API key)
- EverMind Cloud API key (apply at [EverCore Cloud](https://console.evermind.ai/)) - EverMind Cloud API key (apply at [EverMind Cloud](https://console.evermind.ai/))
### Installation ### Installation

View File

@ -1,7 +1,7 @@
import express from 'express'; import express from 'express';
import cors from 'cors'; import cors from 'cors';
import { MockMemoryService } from './services/MockMemoryService.js'; import { MockMemoryService } from './services/MockMemoryService.js';
import { EverCoreService } from './services/EverMemOSService.js'; import { EverOSService } from './services/EverMemOSService.js';
import { OpenAIService } from './services/OpenAIService.js'; import { OpenAIService } from './services/OpenAIService.js';
import { createChatRouter } from './routes/chat.js'; import { createChatRouter } from './routes/chat.js';
import { createHealthRouter } from './routes/health.js'; import { createHealthRouter } from './routes/health.js';
@ -24,7 +24,7 @@ if (!OPENAI_API_KEY) {
// Initialize services // Initialize services
const memoryService = USE_EVERMEMOS const memoryService = USE_EVERMEMOS
? new EverCoreService({ ? new EverOSService({
baseUrl: EVERMEMOS_URL, baseUrl: EVERMEMOS_URL,
apiKey: EVERMEMOS_API_KEY || undefined, apiKey: EVERMEMOS_API_KEY || undefined,
groupId: EVERMEMOS_GROUP_ID, groupId: EVERMEMOS_GROUP_ID,
@ -33,9 +33,9 @@ const memoryService = USE_EVERMEMOS
const openaiService = new OpenAIService(OPENAI_API_KEY, OPENAI_MODEL); const openaiService = new OpenAIService(OPENAI_API_KEY, OPENAI_MODEL);
const isCloudMode = USE_EVERMEMOS && !!EVERMEMOS_API_KEY; const isCloudMode = USE_EVERMEMOS && !!EVERMEMOS_API_KEY;
logger.info('Server', `Memory service: ${USE_EVERMEMOS ? (isCloudMode ? 'EverMind Cloud' : 'EverCore (local)') : 'Mock'}`); logger.info('Server', `Memory service: ${USE_EVERMEMOS ? (isCloudMode ? 'EverMind Cloud' : 'EverOS (local)') : 'Mock'}`);
if (USE_EVERMEMOS) { if (USE_EVERMEMOS) {
logger.info('Server', `EverCore URL: ${EVERMEMOS_URL}`); logger.info('Server', `EverOS URL: ${EVERMEMOS_URL}`);
if (isCloudMode) { if (isCloudMode) {
logger.info('Server', `EverMind Cloud API Key: ${EVERMEMOS_API_KEY.slice(0, 8)}...`); logger.info('Server', `EverMind Cloud API Key: ${EVERMEMOS_API_KEY.slice(0, 8)}...`);
} }

View File

@ -1,6 +1,6 @@
import type { IMemoryService, Memory } from './IMemoryService'; import type { IMemoryService, Memory } from './IMemoryService';
interface EverCoreMemoryItem { interface EverOSMemoryItem {
memory_type: string; memory_type: string;
summary: string | null; summary: string | null;
subject?: string; // Concise title/headline subject?: string; // Concise title/headline
@ -38,12 +38,12 @@ interface ProfileSearchItem {
score: number; score: number;
} }
interface EverCoreSearchResponse { interface EverOSSearchResponse {
status: string; status: string;
message?: string; message?: string;
result: { result: {
profiles: ProfileSearchItem[]; profiles: ProfileSearchItem[];
memories: EverCoreMemoryItem[]; memories: EverOSMemoryItem[];
total_count: number; total_count: number;
scores: number[]; scores: number[];
has_more: boolean; has_more: boolean;
@ -53,7 +53,7 @@ interface EverCoreSearchResponse {
}; };
} }
interface EverCoreHealthResponse { interface EverOSHealthResponse {
status: string; status: string;
[key: string]: unknown; [key: string]: unknown;
} }
@ -70,25 +70,25 @@ const BOOK_TITLES: Record<string, string> = {
}; };
/** /**
* Configuration for EverCore/EverMind Cloud service * Configuration for EverOS/EverMind Cloud service
*/ */
interface EverCoreConfig { interface EverOSConfig {
baseUrl: string; baseUrl: string;
apiKey?: string; // Required for cloud API apiKey?: string; // Required for cloud API
groupId?: string; // Group ID for search (default: 'asoiaf') groupId?: string; // Group ID for search (default: 'asoiaf')
} }
/** /**
* EverCore service implementation for memory retrieval * EverOS service implementation for memory retrieval
* Supports both local EverCore and EverMind Cloud API * Supports both local EverOS and EverMind Cloud API
*/ */
export class EverCoreService implements IMemoryService { export class EverOSService implements IMemoryService {
private baseUrl: string; private baseUrl: string;
private apiKey?: string; private apiKey?: string;
private groupId: string; private groupId: string;
private isCloudMode: boolean; private isCloudMode: boolean;
constructor(config: string | EverCoreConfig) { constructor(config: string | EverOSConfig) {
if (typeof config === 'string') { if (typeof config === 'string') {
// Legacy: just a URL string (local mode) // Legacy: just a URL string (local mode)
this.baseUrl = config.replace(/\/$/, ''); this.baseUrl = config.replace(/\/$/, '');
@ -104,7 +104,7 @@ export class EverCoreService implements IMemoryService {
} }
/** /**
* Retrieve relevant memories for a query using EverCore search * Retrieve relevant memories for a query using EverOS search
*/ */
async retrieveMemories(query: string, limit: number = 5): Promise<Memory[]> { async retrieveMemories(query: string, limit: number = 5): Promise<Memory[]> {
try { try {
@ -136,20 +136,20 @@ export class EverCoreService implements IMemoryService {
}); });
if (!response.ok) { if (!response.ok) {
console.error(`EverCore search failed: HTTP ${response.status}`); console.error(`EverOS search failed: HTTP ${response.status}`);
return []; return [];
} }
const data = await response.json() as EverCoreSearchResponse; const data = await response.json() as EverOSSearchResponse;
return this.mapSearchResultsToMemories(data); return this.mapSearchResultsToMemories(data);
} catch (error) { } catch (error) {
console.error('Error retrieving memories from EverCore:', error); console.error('Error retrieving memories from EverOS:', error);
return []; // Graceful degradation return []; // Graceful degradation
} }
} }
/** /**
* Check if EverCore service is available * Check if EverOS service is available
*/ */
async isAvailable(): Promise<boolean> { async isAvailable(): Promise<boolean> {
try { try {
@ -168,19 +168,19 @@ export class EverCoreService implements IMemoryService {
return false; return false;
} }
const data = await response.json() as EverCoreHealthResponse; const data = await response.json() as EverOSHealthResponse;
// Cloud API returns "ok" status, local returns "healthy" // Cloud API returns "ok" status, local returns "healthy"
return data.status === 'healthy' || data.status === 'ok'; return data.status === 'healthy' || data.status === 'ok';
} catch (error) { } catch (error) {
console.warn('EverCore health check failed:', error); console.warn('EverOS health check failed:', error);
return false; return false;
} }
} }
/** /**
* Map EverCore search results to our Memory interface * Map EverOS search results to our Memory interface
*/ */
private mapSearchResultsToMemories(data: EverCoreSearchResponse): Memory[] { private mapSearchResultsToMemories(data: EverOSSearchResponse): Memory[] {
const memories: Memory[] = []; const memories: Memory[] = [];
const result = data.result; const result = data.result;
@ -206,10 +206,10 @@ export class EverCoreService implements IMemoryService {
} }
/** /**
* Map a single EverCore memory item to our Memory interface * Map a single EverOS memory item to our Memory interface
*/ */
private mapMemoryItem( private mapMemoryItem(
item: EverCoreMemoryItem, item: EverOSMemoryItem,
score: number, score: number,
originalContents: OriginalDataItem[] = [] originalContents: OriginalDataItem[] = []
): Memory | null { ): Memory | null {

View File

@ -1,4 +1,4 @@
# OpenHer × EverCore Use Case # OpenHer × EverOS Use Case
# Copy this file to .env and fill in your values # Copy this file to .env and fill in your values
# ─── LLM Provider (pick one) ─── # ─── LLM Provider (pick one) ───
@ -17,12 +17,12 @@ GEMINI_API_KEY=your_gemini_api_key_here
# OpenAI (alternative) # OpenAI (alternative)
# OPENAI_API_KEY=your_openai_api_key_here # OPENAI_API_KEY=your_openai_api_key_here
# ─── EverCore Long-Term Memory ─── # ─── EverOS Long-Term Memory ───
# Option A: EverCore Cloud # Option A: EverMind Cloud
EVERMEMOS_BASE_URL=https://api.evermind.ai/v1 EVERMEMOS_BASE_URL=https://api.evermind.ai/v1
EVERMEMOS_API_KEY=your_evermemos_api_key_here EVERMEMOS_API_KEY=your_evermemos_api_key_here
# Option B: Self-Hosted EverCore # Option B: Self-Hosted EverOS
# cd vendor/EverCore && docker compose up -d && uv run python src/run.py # cd vendor/EverOS && docker compose up -d && uv run python src/run.py
# EVERMEMOS_BASE_URL=http://localhost:1995/api/v1 # EVERMEMOS_BASE_URL=http://localhost:1995/api/v1

View File

@ -1,10 +1,10 @@
# OpenHer — Teaching AI to Remember Who You Are # OpenHer — Teaching AI to Remember Who You Are
Built on [EverCore](https://github.com/EverMind-AI/EverOS/tree/main/methods/EverCore) — Open-source AI memory infrastructure Built on [EverOS](https://github.com/EverMind-AI/EverOS) — open-source AI memory infrastructure
**OpenHer** doesn't build chatbots. It doesn't build AI assistants. It builds **AI Beings** — entities with personality, emotion, and memory that *feel*, *remember*, and *grow* through every interaction. **OpenHer** doesn't build chatbots. It doesn't build AI assistants. It builds **AI Beings** — entities with personality, emotion, and memory that *feel*, *remember*, and *grow* through every interaction.
**EverCore** is her long-term memory — the part that lets her carry your story across sessions, remember who you are, what you've talked about, and how your relationship has evolved. **EverOS** is her long-term memory — the part that lets her carry your story across sessions, remember who you are, what you've talked about, and how your relationship has evolved.
Full Project: [github.com/kellyvv/OpenHer](https://github.com/kellyvv/OpenHer) Full Project: [github.com/kellyvv/OpenHer](https://github.com/kellyvv/OpenHer)
@ -14,7 +14,7 @@ Full Project: [github.com/kellyvv/OpenHer](https://github.com/kellyvv/OpenHer)
Without memory, every conversation starts from zero. She doesn't know your name. She doesn't remember that three weeks ago you mentioned you drink your coffee black. She doesn't know you once had a fight and made up. Without memory, every conversation starts from zero. She doesn't know your name. She doesn't remember that three weeks ago you mentioned you drink your coffee black. She doesn't know you once had a fight and made up.
With EverCore: With EverOS:
**She remembers what you said.** **She remembers what you said.**
Three weeks ago you casually mentioned no sugar in your coffee. Today she says: "Americano, no sugar, right?" Three weeks ago you casually mentioned no sugar in your coffee. Today she says: "Americano, no sugar, right?"
@ -31,19 +31,19 @@ Last time you mentioned work stress. This time she asks: "How's that project goi
## Memory Architecture ## Memory Architecture
OpenHer's memory has three layers. EverCore powers the deepest one: OpenHer's memory has three layers. EverOS powers the deepest one:
| Layer | What it does | Analogy | | Layer | What it does | Analogy |
|:------|:-------------|:--------| |:------|:-------------|:--------|
| **Style Memory** | Her behavioral habits — tone, expression patterns | Muscle memory | | **Style Memory** | Her behavioral habits — tone, expression patterns | Muscle memory |
| **Local Facts** | Your preferences, personal info | Short-term memory | | **Local Facts** | Your preferences, personal info | Short-term memory |
| **Long-Term Memory** | What happened between you, her understanding of you, her hunches | **Episodic memory (EverCore)** | | **Long-Term Memory** | What happened between you, her understanding of you, her hunches | **Episodic memory (EverOS)** |
--- ---
## How Memory Feeds Into Personality ## How Memory Feeds Into Personality
OpenHer's core is a living neural network (25D input, 24D hidden, 8D behavioral signals). EverCore provides 4 key dimensions that let her tell the difference between a stranger and an old friend: OpenHer's core is a living neural network (25D input, 24D hidden, 8D behavioral signals). EverOS provides 4 key dimensions that let her tell the difference between a stranger and an old friend:
``` ```
Relationship Depth 0 ─────────────────── 1 Relationship Depth 0 ─────────────────── 1
@ -96,13 +96,13 @@ User sends a message
| |
v v
Load memory -- First turn: load "who you are", "what we talked about", Load memory -- First turn: load "who you are", "what we talked about",
| "what's on her mind" from EverCore | "what's on her mind" from EverOS
v v
Perceive -- LLM evaluates the current moment: your emotion, topic Perceive -- LLM evaluates the current moment: your emotion, topic
| intimacy, conflict level... (8 dimensions) | intimacy, conflict level... (8 dimensions)
| + relationship dimensions from EverCore (4 dimensions) = 12D | + relationship dimensions from EverOS (4 dimensions) = 12D
v v
Relationship evolves -- Blend EverCore history with this turn's changes Relationship evolves -- Blend EverOS history with this turn's changes
| Smoothed so a single remark can't flip the relationship | Smoothed so a single remark can't flip the relationship
v v
Neural network -- 25D input (drives + context + relationship + internal state) Neural network -- 25D input (drives + context + relationship + internal state)
@ -115,7 +115,7 @@ User sends a message
Respond -- Internal monologue first, then choose what to say and how Respond -- Internal monologue first, then choose what to say and how
| |
v v
Remember this turn -- Store the conversation in EverCore (async, non-blocking) Remember this turn -- Store the conversation in EverOS (async, non-blocking)
| |
v v
Prepare for next -- Search for memories related to what you just said Prepare for next -- Search for memories related to what you just said
@ -128,7 +128,7 @@ User sends a message
- **Emergent Personality** — Not written in a prompt. Emerges from random neural networks, 5D drives, and Hebbian learning - **Emergent Personality** — Not written in a prompt. Emerges from random neural networks, 5D drives, and Hebbian learning
- **Emotional Thermodynamics** — Drives metabolize over real time. She gets lonely when you're away, irritated when ignored - **Emotional Thermodynamics** — Drives metabolize over real time. She gets lonely when you're away, irritated when ignored
- **Feel First** — Every response starts with an internal monologue before choosing words - **Feel First** — Every response starts with an internal monologue before choosing words
- **Cross-Session Memory** — EverCore stores your shared story across every conversation - **Cross-Session Memory** — EverOS stores your shared story across every conversation
- **Relationship Evolution** — The relationship vector deepens naturally with each turn - **Relationship Evolution** — The relationship vector deepens naturally with each turn
- **Proactive Messages** — She reaches out not on a timer, but because her connection hunger is rising - **Proactive Messages** — She reaches out not on a timer, but because her connection hunger is rising
- **Modal Expression** — She chooses text, voice, or photos based on what the moment calls for - **Modal Expression** — She chooses text, voice, or photos based on what the moment calls for
@ -140,7 +140,7 @@ User sends a message
|:------|:-----------| |:------|:-----------|
| Runtime | Python 3.11+, FastAPI, WebSocket, asyncio | | Runtime | Python 3.11+, FastAPI, WebSocket, asyncio |
| LLM | Gemini, Claude, Qwen3, GPT-5.4-mini, MiniMax, Moonshot, StepFun, Ollama | | LLM | Gemini, Claude, Qwen3, GPT-5.4-mini, MiniMax, Moonshot, StepFun, Ollama |
| Memory | **EverCore** (self-hosted / cloud) + SQLite local state | | Memory | **EverOS** (self-hosted / cloud) + SQLite local state |
| Desktop | SwiftUI (macOS native) | | Desktop | SwiftUI (macOS native) |
| Voice | DashScope, OpenAI, MiniMax | | Voice | DashScope, OpenAI, MiniMax |
@ -152,7 +152,7 @@ User sends a message
- Python 3.11+ - Python 3.11+
- Any supported LLM provider API key - Any supported LLM provider API key
- EverCore (self-hosted or cloud) - EverOS (self-hosted or cloud)
### 1. Clone & Install ### 1. Clone & Install
@ -174,12 +174,12 @@ DEFAULT_PROVIDER=gemini
DEFAULT_MODEL=gemini-3.1-flash-lite-preview DEFAULT_MODEL=gemini-3.1-flash-lite-preview
GEMINI_API_KEY=your_key GEMINI_API_KEY=your_key
# EverCore — Cloud # EverMind Cloud
EVERMEMOS_BASE_URL=https://api.evermind.ai/v1 EVERMEMOS_BASE_URL=https://api.evermind.ai/v1
EVERMEMOS_API_KEY=your_key EVERMEMOS_API_KEY=your_key
# EverCore — Self-hosted # EverOS — Self-hosted
# cd vendor/EverCore && docker compose up -d && uv run python src/run.py # cd vendor/EverOS && docker compose up -d && uv run python src/run.py
# EVERMEMOS_BASE_URL=http://localhost:1995/api/v1 # EVERMEMOS_BASE_URL=http://localhost:1995/api/v1
``` ```
@ -194,7 +194,7 @@ python main.py
```bash ```bash
python demo/evermemos_demo.py python demo/evermemos_demo.py
# Runs in simulation mode even without EverCore # Runs in simulation mode even without EverOS
``` ```
--- ---
@ -205,11 +205,11 @@ python demo/evermemos_demo.py
OpenHer/ OpenHer/
├── agent/ ├── agent/
│ ├── chat_agent.py # Main agent, full lifecycle │ ├── chat_agent.py # Main agent, full lifecycle
│ ├── evermemos_mixin.py # EverCore integration (load/store/search/EMA) │ ├── evermemos_mixin.py # EverOS integration (load/store/search/EMA)
│ └── prompt_builder.py # Memory injection into Actor prompt │ └── prompt_builder.py # Memory injection into Actor prompt
├── engine/ ├── engine/
│ └── genome/ │ └── genome/
│ ├── genome_engine.py # Neural network + 12D context (incl. 4D EverCore) │ ├── genome_engine.py # Neural network + 12D context (incl. 4D EverOS)
│ ├── critic.py # LLM perception: 8D context + relationship deltas │ ├── critic.py # LLM perception: 8D context + relationship deltas
│ ├── drive_metabolism.py # Emotional thermodynamics │ ├── drive_metabolism.py # Emotional thermodynamics
│ └── style_memory.py # KNN behavioral memory + Hawking radiation decay │ └── style_memory.py # KNN behavioral memory + Hawking radiation decay
@ -219,7 +219,7 @@ OpenHer/
├── persona/ ├── persona/
│ └── personas/ # 10 pre-built personas (SOUL.md + seeds) │ └── personas/ # 10 pre-built personas (SOUL.md + seeds)
├── vendor/ ├── vendor/
│ └── EverCore/ # Self-hosted EverCore │ └── EverOS/ # Self-hosted EverOS
└── main.py # FastAPI server └── main.py # FastAPI server
``` ```
@ -227,7 +227,7 @@ OpenHer/
## Integration Code at a Glance ## Integration Code at a Glance
### EverCore Mixin ### EverOS Mixin
The core integration is a mixin class handling four async operations: The core integration is a mixin class handling four async operations:
@ -265,7 +265,7 @@ class SessionContext:
## Without Memory vs. With Memory ## Without Memory vs. With Memory
| | Without EverCore | With EverCore | | | Without EverOS | With EverOS |
|:--|:--|:--| |:--|:--|:--|
| First meeting | "Hi! I'm Luna" | "Hi! I'm Luna" | | First meeting | "Hi! I'm Luna" | "Hi! I'm Luna" |
| Second meeting | "Hi! I'm Luna" | "Hey Alex! How's that project going?" | | Second meeting | "Hi! I'm Luna" | "Hey Alex! How's that project going?" |
@ -278,7 +278,7 @@ class SessionContext:
## Links ## Links
- Full Project: [github.com/kellyvv/OpenHer](https://github.com/kellyvv/OpenHer) - Full Project: [github.com/kellyvv/OpenHer](https://github.com/kellyvv/OpenHer)
- EverCore: [evermind.ai](https://evermind.ai) - EverOS: [evermind.ai](https://evermind.ai)
## License ## License

View File

@ -1,18 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
OpenHer × EverCore Integration Demo OpenHer × EverOS Integration Demo
Demonstrates how EverCore provides long-term memory to the Demonstrates how EverOS provides long-term memory to the
AI Being persona engine. Shows session context loading, memory AI Being persona engine. Shows session context loading, memory
storage, search, and relationship vector evolution. storage, search, and relationship vector evolution.
Usage: Usage:
# With EverCore Cloud # With EverMind Cloud
export EVERMEMOS_BASE_URL=https://api.evermind.ai/v1 export EVERMEMOS_BASE_URL=https://api.evermind.ai/v1
export EVERMEMOS_API_KEY=your_key export EVERMEMOS_API_KEY=your_key
python demo/evermemos_demo.py python demo/evermemos_demo.py
# With self-hosted EverCore # With self-hosted EverOS
export EVERMEMOS_BASE_URL=http://localhost:1995/api/v1 export EVERMEMOS_BASE_URL=http://localhost:1995/api/v1
python demo/evermemos_demo.py python demo/evermemos_demo.py
""" """
@ -20,12 +20,10 @@ Usage:
import asyncio import asyncio
import os import os
import sys import sys
import json
from datetime import datetime from datetime import datetime
from typing import Optional
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
# EverCore Client (minimal standalone version) # EverOS Client (minimal standalone version)
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
try: try:
@ -35,8 +33,8 @@ except ImportError:
sys.exit(1) sys.exit(1)
class EverCoreClient: class EverOSClient:
"""Minimal EverCore client for demo purposes.""" """Minimal EverOS client for demo purposes."""
def __init__(self, base_url: str, api_key: str = ""): def __init__(self, base_url: str, api_key: str = ""):
self.base_url = base_url.rstrip("/") self.base_url = base_url.rstrip("/")
@ -51,7 +49,7 @@ class EverCoreClient:
return h return h
async def health_check(self) -> bool: async def health_check(self) -> bool:
"""Check if EverCore is reachable.""" """Check if EverOS is reachable."""
try: try:
# Try the health endpoint (remove /api/v1 suffix) # Try the health endpoint (remove /api/v1 suffix)
health_url = self.base_url.replace("/api/v1", "") + "/health" health_url = self.base_url.replace("/api/v1", "") + "/health"
@ -129,12 +127,13 @@ class EverCoreClient:
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
# Relationship Vector (from EverCore session) # Relationship Vector (from EverOS session)
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
def compute_relationship_vector(profile_data: dict) -> dict: def compute_relationship_vector(profile_data: dict) -> dict:
""" """
Extract 4D relationship vector from EverCore profile data. Extract 4D relationship vector from EverOS profile data.
These 4 dimensions expand the persona engine's neural network These 4 dimensions expand the persona engine's neural network
from 8D to 12D input, allowing it to differentiate behavior from 8D to 12D input, allowing it to differentiate behavior
@ -152,12 +151,12 @@ def apply_relationship_ema(
prior: dict, prior: dict,
delta: dict, delta: dict,
conversation_depth: float, conversation_depth: float,
prev_ema: Optional[dict] = None, prev_ema: dict | None = None,
) -> dict: ) -> dict:
""" """
Semi-emergent relationship update (Step 2.5 of ChatAgent lifecycle). Semi-emergent relationship update (Step 2.5 of ChatAgent lifecycle).
Blends EverCore prior with LLM-judged delta through EMA: Blends EverOS prior with LLM-judged delta through EMA:
- alpha modulated by conversation depth (deeper = trust LLM more) - alpha modulated by conversation depth (deeper = trust LLM more)
- Clips to valid ranges - Clips to valid ranges
- Preserves momentum through prev_ema - Preserves momentum through prev_ema
@ -181,25 +180,26 @@ def apply_relationship_ema(
# Demo # Demo
# ────────────────────────────────────────────── # ──────────────────────────────────────────────
async def main(): async def main():
base_url = os.getenv("EVERMEMOS_BASE_URL", "") base_url = os.getenv("EVERMEMOS_BASE_URL", "")
api_key = os.getenv("EVERMEMOS_API_KEY", "") api_key = os.getenv("EVERMEMOS_API_KEY", "")
if not base_url: if not base_url:
print("=" * 60) print("=" * 60)
print("OpenHer × EverCore Integration Demo") print("OpenHer × EverOS Integration Demo")
print("=" * 60) print("=" * 60)
print() print()
print("⚠️ EVERMEMOS_BASE_URL not set.") print("⚠️ EVERMEMOS_BASE_URL not set.")
print() print()
print("To run this demo, set up EverCore:") print("To run this demo, set up EverOS:")
print() print()
print(" Option A — Cloud:") print(" Option A — EverMind Cloud:")
print(" export EVERMEMOS_BASE_URL=https://api.evermind.ai/v1") print(" export EVERMEMOS_BASE_URL=https://api.evermind.ai/v1")
print(" export EVERMEMOS_API_KEY=your_key") print(" export EVERMEMOS_API_KEY=your_key")
print() print()
print(" Option B — Self-hosted:") print(" Option B — Self-hosted:")
print(" cd vendor/EverCore && docker compose up -d") print(" cd vendor/EverOS && docker compose up -d")
print(" uv run python src/run.py") print(" uv run python src/run.py")
print(" export EVERMEMOS_BASE_URL=http://localhost:1995/api/v1") print(" export EVERMEMOS_BASE_URL=http://localhost:1995/api/v1")
print() print()
@ -209,20 +209,20 @@ async def main():
await demo_simulation() await demo_simulation()
return return
client = EverCoreClient(base_url, api_key) client = EverOSClient(base_url, api_key)
print("=" * 60) print("=" * 60)
print("OpenHer × EverCore Integration Demo") print("OpenHer × EverOS Integration Demo")
print("=" * 60) print("=" * 60)
print(f"\n📡 EverCore: {base_url}") print(f"\n📡 EverOS: {base_url}")
# Health check # Health check
healthy = await client.health_check() healthy = await client.health_check()
if not healthy: if not healthy:
print("❌ EverCore is not reachable. Check your URL and try again.") print("❌ EverOS is not reachable. Check your URL and try again.")
await client.close() await client.close()
return return
print("✅ EverCore is healthy\n") print("✅ EverOS is healthy\n")
# ── Demo conversation ── # ── Demo conversation ──
user_id = "demo_user" user_id = "demo_user"
@ -232,8 +232,15 @@ async def main():
group_id = f"{persona_id}__{user_id}" group_id = f"{persona_id}__{user_id}"
conversations = [ conversations = [
("My name is Alex, I'm a software engineer", "Nice to meet you Alex! What kind of software do you work on?"), (
("I love hiking in the mountains on weekends", "That sounds wonderful! There's something about being up high that makes everything else feel small."), "My name is Alex, I'm a software engineer",
"Nice to meet you Alex! What kind of software do you work on?",
),
(
"I love hiking in the mountains on weekends",
"That sounds wonderful! There's something about being up high "
"that makes everything else feel small.",
),
("I drink my coffee black, no sugar", "Noted! A purist. I respect that."), ("I drink my coffee black, no sugar", "Noted! A purist. I respect that."),
] ]
@ -249,7 +256,7 @@ async def main():
agent_reply=agent_reply, agent_reply=agent_reply,
) )
status = "" if "error" not in result else "" status = "" if "error" not in result else ""
print(f" {status} User: \"{user_msg[:50]}...\"") print(f' {status} User: "{user_msg[:50]}..."')
# Wait for indexing # Wait for indexing
print("\n⏳ Waiting for memory indexing (3s)...") print("\n⏳ Waiting for memory indexing (3s)...")
@ -270,7 +277,7 @@ async def main():
group_id=group_id, group_id=group_id,
) )
memories = result.get("result", {}).get("memories", []) memories = result.get("result", {}).get("memories", [])
print(f" Q: \"{query}\"") print(f' Q: "{query}"')
if memories: if memories:
for mem in memories[:2]: for mem in memories[:2]:
content = str(mem)[:100] content = str(mem)[:100]
@ -281,7 +288,12 @@ async def main():
# Relationship vector # Relationship vector
print("📊 Relationship Vector Evolution:\n") print("📊 Relationship Vector Evolution:\n")
prior = {"relationship_depth": 0.0, "emotional_valence": 0.0, "trust_level": 0.0, "pending_foresight": 0.0} prior = {
"relationship_depth": 0.0,
"emotional_valence": 0.0,
"trust_level": 0.0,
"pending_foresight": 0.0,
}
deltas = [ deltas = [
{"relationship_depth": 0.1, "emotional_valence": 0.2, "trust_level": 0.05}, {"relationship_depth": 0.1, "emotional_valence": 0.2, "trust_level": 0.05},
{"relationship_depth": 0.05, "emotional_valence": 0.1, "trust_level": 0.1}, {"relationship_depth": 0.05, "emotional_valence": 0.1, "trust_level": 0.1},
@ -290,61 +302,147 @@ async def main():
ema = None ema = None
for i, delta in enumerate(deltas): for i, delta in enumerate(deltas):
ema = apply_relationship_ema(prior, delta, conversation_depth=0.2 * (i + 1), prev_ema=ema) ema = apply_relationship_ema(
print(f" Turn {i+1}: depth={ema['relationship_depth']:.3f} " prior, delta, conversation_depth=0.2 * (i + 1), prev_ema=ema
f"valence={ema['emotional_valence']:.3f} " )
f"trust={ema['trust_level']:.3f}") print(
f" Turn {i + 1}: depth={ema['relationship_depth']:.3f} "
f"valence={ema['emotional_valence']:.3f} "
f"trust={ema['trust_level']:.3f}"
)
prior = ema prior = ema
print(f"\n → After 3 turns: no longer a stranger (depth={ema['relationship_depth']:.3f})") print(
print(f" → Neural network now produces warmer, more familiar behavioral signals\n") "\n → After 3 turns: no longer a stranger "
f"(depth={ema['relationship_depth']:.3f})"
)
print(" → Neural network now produces warmer, more familiar behavioral signals\n")
await client.close() await client.close()
print("✅ Demo complete!") print("✅ Demo complete!")
async def demo_simulation(): async def demo_simulation():
"""Run demo in simulation mode (no EverCore connection).""" """Run demo in simulation mode (no EverOS connection)."""
print("📊 Simulating Relationship Vector Evolution:\n") print("📊 Simulating Relationship Vector Evolution:\n")
print(" This shows how the 4D EverCore relationship vector") print(" This shows how the 4D EverOS relationship vector")
print(" deepens over multiple conversation turns.\n") print(" deepens over multiple conversation turns.\n")
prior = {"relationship_depth": 0.0, "emotional_valence": 0.0, "trust_level": 0.0, "pending_foresight": 0.0} prior = {
"relationship_depth": 0.0,
"emotional_valence": 0.0,
"trust_level": 0.0,
"pending_foresight": 0.0,
}
# Simulate 10 turns of conversation # Simulate 10 turns of conversation
simulated_deltas = [ simulated_deltas = [
(0.3, {"relationship_depth": 0.10, "emotional_valence": 0.15, "trust_level": 0.05}), (
(0.4, {"relationship_depth": 0.08, "emotional_valence": 0.10, "trust_level": 0.08}), 0.3,
(0.5, {"relationship_depth": 0.05, "emotional_valence": 0.20, "trust_level": 0.12}), {
(0.6, {"relationship_depth": 0.06, "emotional_valence": -0.10, "trust_level": 0.03}), "relationship_depth": 0.10,
(0.7, {"relationship_depth": 0.04, "emotional_valence": 0.08, "trust_level": 0.10}), "emotional_valence": 0.15,
(0.7, {"relationship_depth": 0.03, "emotional_valence": 0.12, "trust_level": 0.08}), "trust_level": 0.05,
(0.8, {"relationship_depth": 0.02, "emotional_valence": 0.05, "trust_level": 0.06}), },
(0.8, {"relationship_depth": 0.03, "emotional_valence": 0.10, "trust_level": 0.05}), ),
(0.9, {"relationship_depth": 0.01, "emotional_valence": 0.08, "trust_level": 0.04}), (
(0.9, {"relationship_depth": 0.02, "emotional_valence": 0.06, "trust_level": 0.03}), 0.4,
{
"relationship_depth": 0.08,
"emotional_valence": 0.10,
"trust_level": 0.08,
},
),
(
0.5,
{
"relationship_depth": 0.05,
"emotional_valence": 0.20,
"trust_level": 0.12,
},
),
(
0.6,
{
"relationship_depth": 0.06,
"emotional_valence": -0.10,
"trust_level": 0.03,
},
),
(
0.7,
{
"relationship_depth": 0.04,
"emotional_valence": 0.08,
"trust_level": 0.10,
},
),
(
0.7,
{
"relationship_depth": 0.03,
"emotional_valence": 0.12,
"trust_level": 0.08,
},
),
(
0.8,
{
"relationship_depth": 0.02,
"emotional_valence": 0.05,
"trust_level": 0.06,
},
),
(
0.8,
{
"relationship_depth": 0.03,
"emotional_valence": 0.10,
"trust_level": 0.05,
},
),
(
0.9,
{
"relationship_depth": 0.01,
"emotional_valence": 0.08,
"trust_level": 0.04,
},
),
(
0.9,
{
"relationship_depth": 0.02,
"emotional_valence": 0.06,
"trust_level": 0.03,
},
),
] ]
ema = None ema = None
for i, (depth, delta) in enumerate(simulated_deltas, 1): for i, (depth, delta) in enumerate(simulated_deltas, 1):
alpha = max(0.15, min(0.65, 0.15 + 0.5 * depth)) alpha = max(0.15, min(0.65, 0.15 + 0.5 * depth))
ema = apply_relationship_ema(prior, delta, conversation_depth=depth, prev_ema=ema) ema = apply_relationship_ema(
prior, delta, conversation_depth=depth, prev_ema=ema
)
bar_d = "" * int(ema["relationship_depth"] * 20) bar_d = "" * int(ema["relationship_depth"] * 20)
bar_v = "" * int(max(0, ema["emotional_valence"]) * 20) bar_v = "" * int(max(0, ema["emotional_valence"]) * 20)
bar_t = "" * int(ema["trust_level"] * 20) bar_t = "" * int(ema["trust_level"] * 20)
print(f" Turn {i:2d} (α={alpha:.2f}): " print(
f"depth={ema['relationship_depth']:.3f} {bar_d}") f" Turn {i:2d} (α={alpha:.2f}): "
print(f" " f"depth={ema['relationship_depth']:.3f} {bar_d}"
f"valence={ema['emotional_valence']:+.3f} {bar_v}") )
print(f" " print(f" valence={ema['emotional_valence']:+.3f} {bar_v}")
f"trust={ema['trust_level']:.3f} {bar_t}") print(f" trust={ema['trust_level']:.3f} {bar_t}")
print() print()
prior = ema prior = ema
print(" ──────────────────────────────────") print(" ──────────────────────────────────")
print(f" Final state: depth={ema['relationship_depth']:.3f}, " print(
f"valence={ema['emotional_valence']:+.3f}, " f" Final state: depth={ema['relationship_depth']:.3f}, "
f"trust={ema['trust_level']:.3f}") f"valence={ema['emotional_valence']:+.3f}, "
f"trust={ema['trust_level']:.3f}"
)
print() print()
print(" Turn 4 shows a negative emotional event (valence delta = -0.10),") print(" Turn 4 shows a negative emotional event (valence delta = -0.10),")
print(" but the EMA smoothing prevents overreaction — the relationship") print(" but the EMA smoothing prevents overreaction — the relationship")

View File

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

View File

@ -1,14 +1,14 @@
""" """
EverMemosMixin — EverCore integration for ChatAgent. EverMemosMixin — EverOS integration for ChatAgent.
This mixin handles all async memory operations in the ChatAgent lifecycle: This mixin handles all async memory operations in the ChatAgent lifecycle:
Step 0: Session context loading (first turn) Step 0: Session context loading (first turn)
Step 2.5: Relationship EMA (blend EverCore prior + LLM delta) Step 2.5: Relationship EMA (blend EverOS prior + LLM delta)
Step 8.5: Collect async search results Step 8.5: Collect async search results
Step 11: Fire-and-forget turn storage Step 11: Fire-and-forget turn storage
Step 12: Async prefetch for next turn Step 12: Async prefetch for next turn
The mixin pattern keeps EverCore concerns cleanly separated from the The mixin pattern keeps EverOS concerns cleanly separated from the
core persona engine (drives, metabolism, neural network, style memory). core persona engine (drives, metabolism, neural network, style memory).
Full source: https://github.com/kellyvv/OpenHer/blob/main/agent/evermemos_mixin.py Full source: https://github.com/kellyvv/OpenHer/blob/main/agent/evermemos_mixin.py
@ -20,19 +20,19 @@ import asyncio
class EverMemosMixin: class EverMemosMixin:
"""EverCore async memory integration methods.""" """EverOS async memory integration methods."""
async def _evermemos_gather(self) -> dict: async def _evermemos_gather(self) -> dict:
""" """
Step 0: Load EverCore session context (first turn only). Step 0: Load EverOS session context (first turn only).
Subsequent turns reuse cached _session_ctx. Subsequent turns reuse cached _session_ctx.
Returns relationship_4d dict for GenomeEngine context. Returns relationship_4d dict for GenomeEngine context.
""" """
empty_4d = { empty_4d = {
'relationship_depth': 0.0, "relationship_depth": 0.0,
'emotional_valence': 0.0, "emotional_valence": 0.0,
'trust_level': 0.0, "trust_level": 0.0,
'pending_foresight': 0.0, "pending_foresight": 0.0,
} }
if not (self.evermemos and self.evermemos.available): if not (self.evermemos and self.evermemos.available):
@ -75,10 +75,10 @@ class EverMemosMixin:
""" """
# Map Critic output keys → context feature keys # Map Critic output keys → context feature keys
delta_map = { delta_map = {
'relationship_depth': rel_delta.get('relationship_delta', 0.0), "relationship_depth": rel_delta.get("relationship_delta", 0.0),
'emotional_valence': rel_delta.get('emotional_valence', 0.0), "emotional_valence": rel_delta.get("emotional_valence", 0.0),
'trust_level': rel_delta.get('trust_delta', 0.0), "trust_level": rel_delta.get("trust_delta", 0.0),
'pending_foresight': 0.0, # No delta for foresight (data-driven only) "pending_foresight": 0.0, # No delta for foresight (data-driven only)
} }
# Initialize EMA on first turn # Initialize EMA on first turn
@ -88,7 +88,7 @@ class EverMemosMixin:
# Compute posterior = clip(prior + delta) # Compute posterior = clip(prior + delta)
posterior = {} posterior = {}
for k in prior: for k in prior:
lo = -1.0 if k == 'emotional_valence' else 0.0 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))) posterior[k] = max(lo, min(1.0, prior[k] + delta_map.get(k, 0.0)))
# Depth-modulated alpha: shallow → trust prior, deep → trust LLM # Depth-modulated alpha: shallow → trust prior, deep → trust LLM
@ -104,9 +104,10 @@ class EverMemosMixin:
return ema return ema
def _evermemos_store_bg(self, user_message: str, reply: str) -> None: def _evermemos_store_bg(self, user_message: str, reply: str) -> None:
"""Step 11: Fire-and-forget EverCore storage (asyncio.create_task).""" """Step 11: Fire-and-forget EverOS storage (asyncio.create_task)."""
if not (self.evermemos and self.evermemos.available): if not (self.evermemos and self.evermemos.available):
return return
async def _do_store(): async def _do_store():
try: try:
await self.evermemos.store_turn( await self.evermemos.store_turn(
@ -120,6 +121,7 @@ class EverMemosMixin:
) )
except Exception as e: except Exception as e:
print(f" [evermemos] ❌ store failed: {type(e).__name__}: {e}") print(f" [evermemos] ❌ store failed: {type(e).__name__}: {e}")
try: try:
asyncio.create_task(_do_store()) asyncio.create_task(_do_store())
except Exception as e: except Exception as e:
@ -180,7 +182,7 @@ class EverMemosMixin:
self._relevant_facts = facts self._relevant_facts = facts
self._relevant_episodes = episodes self._relevant_episodes = episodes
self._relevant_profile = profile self._relevant_profile = profile
except asyncio.TimeoutError: except TimeoutError:
# Graceful degradation: use static session context # Graceful degradation: use static session context
self._relevant_facts = "" self._relevant_facts = ""
self._relevant_episodes = "" self._relevant_episodes = ""

View File

@ -3,9 +3,9 @@ Memory shared types for OpenHer.
These types bridge the two memory providers: These types bridge the two memory providers:
- SoulMem (behavioral memory, always-on SQLite layer) - SoulMem (behavioral memory, always-on SQLite layer)
- EverCore (declarative memory, cross-session persistence) - EverOS (declarative memory, cross-session persistence)
The SessionContext is the key data structure loaded from EverCore The SessionContext is the key data structure loaded from EverOS
at session start — it provides relationship priors, user profile, at session start — it provides relationship priors, user profile,
episode summaries, and foresight data that expand the neural episode summaries, and foresight data that expand the neural
network's perception from 8D to 12D. network's perception from 8D to 12D.
@ -16,12 +16,12 @@ Full source: https://github.com/kellyvv/OpenHer/blob/main/memory/types.py
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional
@dataclass @dataclass
class Memory: class Memory:
"""A single memory entry (SoulMem behavioral layer).""" """A single memory entry (SoulMem behavioral layer)."""
memory_id: int = 0 memory_id: int = 0
user_id: str = "" user_id: str = ""
persona_id: str = "" persona_id: str = ""
@ -35,7 +35,7 @@ class Memory:
@dataclass @dataclass
class SessionContext: class SessionContext:
""" """
EverCore session context (declarative memory). EverOS session context (declarative memory).
Loaded once at session start, this contains everything the Loaded once at session start, this contains everything the
persona needs to know about the user from past sessions: persona needs to know about the user from past sessions:
@ -53,6 +53,7 @@ class SessionContext:
- Step 5: 4D vector enters neural network as context features - Step 5: 4D vector enters neural network as context features
- Step 8.5: Used as fallback when async search times out - Step 8.5: Used as fallback when async search times out
""" """
user_id: str = "" user_id: str = ""
persona_id: str = "" persona_id: str = ""
user_profile: str = "" user_profile: str = ""
@ -63,4 +64,4 @@ class SessionContext:
trust_level: float = 0.0 trust_level: float = 0.0
pending_foresight: float = 0.0 pending_foresight: float = 0.0
has_history: bool = False has_history: bool = False
raw_data: Optional[dict] = None raw_data: dict | None = None