Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,46 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).

## [2.2.0b1] - 2026-03-31 — Architectural Cleanup

Beta release focused on internal discipline rather than new features.

### Changed — Architecture

- **main.py decomposition**: Extracted `SearchPipeline`, `MemoryWritePipeline`, `OrchestrationEngine` from the 6,129-line monolith. main.py is now ~3,100 lines (49% reduction).
- **Public surface**: `dhee/__init__.py` rewritten for clean, narrow exports. `Memory = CoreMemory` (not FullMemory). Cognitive subsystems intentionally kept internal.
- **MCP split**: `mcp_slim.py` (4-tool product surface) vs `mcp_server.py` (24-tool power surface). Clear separation of concerns.

### Changed — Rename Debt

- All `FADEM_*` env vars → `DHEE_*` (with `FADEM_*` fallback for backward compat).
- Internal `fadem_config` → `fade_config` across memory package.
- Default collection name `fadem_memories` → `dhee_memories`.
- Config field `MemoryConfig.engram` → `MemoryConfig.fade`.
- CLI, MCP server, observability, presets: all `engram` product references removed.

### Added — D2Skill Policy Improvements

- **Dual-granularity policies**: `PolicyGranularity.TASK` (strategy) vs `PolicyGranularity.STEP` (local correction). Inspired by D2Skill (arXiv:2603.28716).
- **Utility scoring**: EMA-smoothed performance delta tracking on policies. Three-signal retrieval ranking (condition match + sigmoid utility + UCB exploration bonus).
- **Utility-based pruning**: `PolicyStore.prune()` removes deprecated policies first, protects validated ones.
- **Buddhi wiring**: `reflect()` accepts `outcome_score`, computes baseline vs actual delta, feeds it to policy `record_outcome()`.

### Fixed

- `buddhi.py`: Replaced dead `memory.get_last_session_digest()` call with working `get_last_session()` import.
- `mcp_server.py`: Fixed wrong "8 tools total" comment (actually 24), fixed `-> Memory` type hints (Memory not imported).
- CLI: Wired `benchmark` command into parser (existed but was unreachable).
- `PolicyStore.prune()`: Fixed bug where `candidates.pop()` pruned validated policies instead of deprecated ones.
- CHANGELOG 2.1.0: Removed false "Production/Stable" and "A-grade" claims.

### Changed — Packaging

- Version: 2.1.0 → 2.2.0b1
- Classifier: `Development Status :: 4 - Beta` (was falsely claiming Production/Stable)

---

## [2.1.0] - 2026-03-30 — Cognition Primitives

Dhee V2.1: Adds first-class cognitive primitives (episodes, tasks, policies, beliefs, triggers) and a 60-test suite that exercises them. These are internal building blocks — the public API remains the 4-operation surface (remember/recall/context/checkpoint).
Expand Down
5 changes: 5 additions & 0 deletions dhee/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
dhee categories List categories
dhee export Export to JSON
dhee import <file> Import from JSON
dhee benchmark Run performance benchmarks
dhee status Version, config, DB info
"""

Expand Down Expand Up @@ -457,6 +458,9 @@ def build_parser() -> argparse.ArgumentParser:
p_status = sub.add_parser("status", help="Show version, config, and agents")
p_status.add_argument("--json", action="store_true", help="JSON output")

# benchmark
sub.add_parser("benchmark", help="Run performance benchmarks")

# uninstall
sub.add_parser("uninstall", help="Remove ~/.dhee directory")

Expand All @@ -477,6 +481,7 @@ def build_parser() -> argparse.ArgumentParser:
"export": cmd_export,
"import": cmd_import,
"status": cmd_status,
"benchmark": cmd_benchmark,
"uninstall": cmd_uninstall,
}

Expand Down
4 changes: 2 additions & 2 deletions dhee/cli_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,15 @@ def get_memory_instance(config: Optional[Dict[str, Any]] = None):
provider="sqlite_vec",
config={
"path": vec_db_path,
"collection_name": "fadem_memories",
"collection_name": "dhee_memories",
"embedding_model_dims": embedding_dims,
},
),
llm=LLMConfig(provider=provider, config=llm_cfg),
embedder=EmbedderConfig(provider=provider, config=embedder_cfg),
history_db_path=history_db_path,
embedding_model_dims=embedding_dims,
engram=FadeMemConfig(enable_forgetting=True),
fade=FadeMemConfig(enable_forgetting=True),
)

return FullMemory(memory_config)
6 changes: 3 additions & 3 deletions dhee/configs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class VectorStoreConfig(BaseModel):
config: Dict[str, Any] = Field(
default_factory=lambda: {
"path": os.path.join(_dhee_data_dir(), "zvec"),
"collection_name": "fadem_memories",
"collection_name": "dhee_memories",
}
)

Expand Down Expand Up @@ -605,15 +605,15 @@ class MemoryConfig(BaseModel):
history_db_path: str = Field(
default_factory=lambda: os.path.join(_dhee_data_dir(), "history.db")
)
collection_name: str = "fadem_memories"
collection_name: str = "dhee_memories"
embedding_model_dims: int = 4096 # nvidia/nv-embed-v1 default dimensions
version: str = "v1.4" # Updated for CLS Distillation Memory
custom_fact_extraction_prompt: Optional[str] = None
custom_conflict_prompt: Optional[str] = None
custom_fusion_prompt: Optional[str] = None
custom_echo_prompt: Optional[str] = None
custom_category_prompt: Optional[str] = None
engram: FadeMemConfig = Field(default_factory=FadeMemConfig)
fade: FadeMemConfig = Field(default_factory=FadeMemConfig)
echo: EchoMemConfig = Field(default_factory=EchoMemConfig)
category: CategoryMemConfig = Field(default_factory=CategoryMemConfig)
scope: ScopeConfig = Field(default_factory=ScopeConfig)
Expand Down
4 changes: 2 additions & 2 deletions dhee/configs/presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def minimal_config():
history_db_path=os.path.join(data_dir, "history.db"),
collection_name="dhee_memories",
embedding_model_dims=384,
engram=FadeMemConfig(enable_forgetting=True),
fade=FadeMemConfig(enable_forgetting=True),
echo=EchoMemConfig(enable_echo=False),
category=CategoryMemConfig(enable_categories=False),
graph=KnowledgeGraphConfig(enable_graph=False),
Expand Down Expand Up @@ -122,7 +122,7 @@ def smart_config():
history_db_path=os.path.join(data_dir, "history.db"),
collection_name="dhee_memories",
embedding_model_dims=dims,
engram=FadeMemConfig(enable_forgetting=True),
fade=FadeMemConfig(enable_forgetting=True),
echo=EchoMemConfig(enable_echo=has_llm),
category=CategoryMemConfig(enable_categories=has_llm),
graph=KnowledgeGraphConfig(enable_graph=True, use_llm_extraction=False),
Expand Down
12 changes: 6 additions & 6 deletions dhee/core/forgetting.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ def __init__(
self,
db: "SQLiteManager",
config: "DistillationConfig",
fadem_config: "FadeMemConfig",
fade_config: "FadeMemConfig",
resolve_conflict_fn=None,
search_fn=None,
llm=None,
):
self.db = db
self.config = config
self.fadem_config = fadem_config
self.fade_config = fade_config
self.resolve_conflict_fn = resolve_conflict_fn
self.search_fn = search_fn
self.llm = llm
Expand Down Expand Up @@ -90,7 +90,7 @@ def run(
nearest = neighbors[0]
similarity = float(nearest.score)

if similarity < self.fadem_config.conflict_similarity_threshold:
if similarity < self.fade_config.conflict_similarity_threshold:
continue

# Fetch the neighbor memory from DB
Expand Down Expand Up @@ -319,12 +319,12 @@ def __init__(
self,
db: "SQLiteManager",
config: "DistillationConfig",
fadem_config: "FadeMemConfig",
fade_config: "FadeMemConfig",
delete_fn=None,
):
self.db = db
self.config = config
self.fadem_config = fadem_config
self.fade_config = fade_config
self.delete_fn = delete_fn

def run(
Expand Down Expand Up @@ -373,7 +373,7 @@ def run(
pressure = strength * pressure_factor * excess_ratio
new_strength = max(0.0, strength - pressure)

if new_strength < self.fadem_config.forgetting_threshold:
if new_strength < self.fade_config.forgetting_threshold:
if self.delete_fn:
try:
self.delete_fn(memory["id"])
Expand Down
54 changes: 26 additions & 28 deletions dhee/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def _get_embedding_dims_for_model(model: str, provider: str) -> int:
"text-embedding-3-large": 3072,
"text-embedding-ada-002": 1536,
}
env_dims = os.environ.get("FADEM_EMBEDDING_DIMS")
env_dims = os.environ.get("DHEE_EMBEDDING_DIMS") or os.environ.get("FADEM_EMBEDDING_DIMS")
if env_dims:
return int(env_dims)
if model in EMBEDDING_DIMS:
Expand All @@ -69,8 +69,8 @@ def _get_embedding_dims_for_model(model: str, provider: str) -> int:
return 3072


def get_memory_instance() -> Memory:
"""Create and return a configured Memory instance (FullMemory for MCP)."""
def get_memory_instance() -> FullMemory:
"""Create and return a configured FullMemory instance for the MCP server."""
openai_key = os.environ.get("OPENAI_API_KEY")
gemini_key = os.environ.get("GOOGLE_API_KEY") or os.environ.get("GEMINI_API_KEY")
nvidia_key = (
Expand All @@ -79,13 +79,17 @@ def get_memory_instance() -> Memory:
or os.environ.get("NVIDIA_EMBEDDING_API_KEY")
)

def _env(key: str, default: str = "") -> str:
"""Read DHEE_ env var with FADEM_ fallback for backward compat."""
return os.environ.get(f"DHEE_{key}") or os.environ.get(f"FADEM_{key}") or default

if openai_key:
embedder_model = os.environ.get("FADEM_EMBEDDER_MODEL", "text-embedding-3-small")
embedder_model = _env("EMBEDDER_MODEL", "text-embedding-3-small")
embedding_dims = _get_embedding_dims_for_model(embedder_model, "openai")
llm_config = LLMConfig(
provider="openai",
config={
"model": os.environ.get("FADEM_LLM_MODEL", "gpt-4o-mini"),
"model": _env("LLM_MODEL", "gpt-4o-mini"),
"temperature": 0.1, "max_tokens": 1024, "api_key": openai_key,
}
)
Expand All @@ -94,12 +98,12 @@ def get_memory_instance() -> Memory:
config={"model": embedder_model, "api_key": openai_key},
)
elif gemini_key:
embedder_model = os.environ.get("FADEM_EMBEDDER_MODEL", "gemini-embedding-001")
embedder_model = _env("EMBEDDER_MODEL", "gemini-embedding-001")
embedding_dims = _get_embedding_dims_for_model(embedder_model, "gemini")
llm_config = LLMConfig(
provider="gemini",
config={
"model": os.environ.get("FADEM_LLM_MODEL", "gemini-2.0-flash"),
"model": _env("LLM_MODEL", "gemini-2.0-flash"),
"temperature": 0.1, "max_tokens": 1024, "api_key": gemini_key,
}
)
Expand All @@ -108,13 +112,12 @@ def get_memory_instance() -> Memory:
config={"model": embedder_model, "api_key": gemini_key},
)
elif nvidia_key:
# Internal provider — not customer-documented
embedder_model = os.environ.get("FADEM_EMBEDDER_MODEL", "nvidia/llama-nemotron-embed-vl-1b-v2")
embedder_model = _env("EMBEDDER_MODEL", "nvidia/llama-nemotron-embed-vl-1b-v2")
embedding_dims = 2048
llm_config = LLMConfig(
provider="nvidia",
config={
"model": os.environ.get("FADEM_LLM_MODEL", "qwen/qwen3.5-397b-a17b"),
"model": _env("LLM_MODEL", "qwen/qwen3.5-397b-a17b"),
"temperature": 0.2, "max_tokens": 4096, "api_key": nvidia_key,
}
)
Expand All @@ -131,17 +134,15 @@ def get_memory_instance() -> Memory:
)

from dhee.configs.base import _dhee_data_dir
vec_db_path = os.environ.get(
"FADEM_VEC_DB_PATH",
os.path.join(_dhee_data_dir(), "zvec"),
)
vec_db_path = _env("VEC_DB_PATH") or os.path.join(_dhee_data_dir(), "zvec")
collection = _env("COLLECTION", "dhee_memories")

# Use in-memory vector store for simple embedder (no persistent storage needed)
if embedder_config.provider == "simple":
vector_store_config = VectorStoreConfig(
provider="memory",
config={
"collection_name": os.environ.get("FADEM_COLLECTION", "fadem_memories"),
"collection_name": collection,
"embedding_model_dims": embedding_dims,
},
)
Expand All @@ -150,20 +151,17 @@ def get_memory_instance() -> Memory:
provider="zvec",
config={
"path": vec_db_path,
"collection_name": os.environ.get("FADEM_COLLECTION", "fadem_memories"),
"collection_name": collection,
"embedding_model_dims": embedding_dims,
},
)

history_db_path = os.environ.get(
"FADEM_HISTORY_DB",
os.path.join(_dhee_data_dir(), "history.db"),
)
history_db_path = _env("HISTORY_DB") or os.path.join(_dhee_data_dir(), "history.db")

fadem_config = FadeMemConfig(
enable_forgetting=os.environ.get("FADEM_ENABLE_FORGETTING", "true").lower() == "true",
sml_decay_rate=float(os.environ.get("FADEM_SML_DECAY_RATE", "0.15")),
lml_decay_rate=float(os.environ.get("FADEM_LML_DECAY_RATE", "0.02")),
fade_config = FadeMemConfig(
enable_forgetting=_env("ENABLE_FORGETTING", "true").lower() == "true",
sml_decay_rate=float(_env("SML_DECAY_RATE", "0.15")),
lml_decay_rate=float(_env("LML_DECAY_RATE", "0.02")),
)

config = MemoryConfig(
Expand All @@ -172,18 +170,18 @@ def get_memory_instance() -> Memory:
embedder=embedder_config,
history_db_path=history_db_path,
embedding_model_dims=embedding_dims,
engram=fadem_config,
fade=fade_config,
)

return FullMemory(config)


# Global instances (lazy)
_memory: Optional[Memory] = None
_memory: Optional[FullMemory] = None
_buddhi = None # type: ignore


def get_memory() -> Memory:
def get_memory() -> FullMemory:
global _memory
if _memory is None:
_memory = get_memory_instance()
Expand All @@ -203,7 +201,7 @@ def get_buddhi():

server = Server("dhee")

# Tool definitions — 8 tools total
# Tool definitions — 24 tools
TOOLS = [
Tool(
name="remember",
Expand Down
14 changes: 7 additions & 7 deletions dhee/memory/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def __init__(
self.vector_store = VectorStoreFactory.create(
self.config.vector_store.provider, self.config.vector_store.config
)
self.fadem_config = self.config.engram
self.fade_config = self.config.fade
self.distillation_config = getattr(self.config, "distillation", None)

# Query embedding LRU cache
Expand Down Expand Up @@ -115,7 +115,7 @@ def add(
# Boost fast trace if multi-trace is enabled
if self.distillation_config and self.distillation_config.enable_multi_trace:
s_fast = existing.get("s_fast") or 0.0
boosted = boost_fast_trace(s_fast, self.fadem_config.access_strength_boost)
boosted = boost_fast_trace(s_fast, self.fade_config.access_strength_boost)
self.db.update_memory(existing["id"], {"s_fast": boosted})
return {
"results": [{
Expand Down Expand Up @@ -162,7 +162,7 @@ def add(
"confidentiality_scope": metadata.get("confidentiality_scope", "work"),
"source_type": "mcp",
"source_app": source_app,
"decay_lambda": self.fadem_config.sml_decay_rate,
"decay_lambda": self.fade_config.sml_decay_rate,
"status": "active",
"importance": metadata.get("importance", 0.5),
"sensitivity": metadata.get("sensitivity", "normal"),
Expand Down Expand Up @@ -374,11 +374,11 @@ def apply_decay(
last_accessed=mem.get("last_accessed", mem.get("created_at", "")),
access_count=int(mem.get("access_count", 0)),
layer=mem.get("layer", "sml"),
config=self.fadem_config,
config=self.fade_config,
)

if should_forget(new_strength, self.fadem_config):
if self.fadem_config.use_tombstone_deletion:
if should_forget(new_strength, self.fade_config):
if self.fade_config.use_tombstone_deletion:
self.db.update_memory(mem["id"], {"tombstone": 1, "strength": new_strength})
else:
self.db.delete_memory(mem["id"])
Expand All @@ -391,7 +391,7 @@ def apply_decay(
mem.get("layer", "sml"),
int(mem.get("access_count", 0)),
new_strength,
self.fadem_config,
self.fade_config,
):
self.db.update_memory(mem["id"], {"strength": new_strength, "layer": "lml"})
promoted += 1
Expand Down
Loading
Loading