Skip to content

Commit 3eaeb78

Browse files
committed
V2.2.5
1 parent afc80a2 commit 3eaeb78

9 files changed

Lines changed: 1386 additions & 140 deletions

File tree

dhee/adapters/base.py

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ def __init__(
9090
from dhee.core.session_tracker import SessionTracker
9191
self._tracker = SessionTracker()
9292

93+
# Hook registry for harness integration
94+
self._hooks: Dict[str, List[Callable]] = self._init_hooks()
95+
9396
# Session tracking (kept for backward compat with session_start/session_end)
9497
self._session_id: Optional[str] = None
9598
self._session_start_time: Optional[float] = None
@@ -110,6 +113,46 @@ def provider(self) -> str:
110113
def buddhi(self):
111114
return self._buddhi
112115

116+
@property
117+
def kernel(self):
118+
"""Access the CognitionKernel for direct state manipulation."""
119+
return self._kernel
120+
121+
# ------------------------------------------------------------------
122+
# Hook registry
123+
# ------------------------------------------------------------------
124+
125+
_HOOK_EVENTS = frozenset([
126+
"pre_remember", "post_remember",
127+
"pre_recall", "post_recall",
128+
"pre_context", "post_context",
129+
"pre_checkpoint", "post_checkpoint",
130+
])
131+
132+
def _init_hooks(self) -> Dict[str, List[Callable]]:
133+
"""Create a fresh hook registry."""
134+
return {event: [] for event in self._HOOK_EVENTS}
135+
136+
def register_hook(self, event: str, callback: Callable) -> None:
137+
"""Register a callback for a lifecycle event.
138+
139+
Events: pre_remember, post_remember, pre_recall, post_recall,
140+
pre_context, post_context, pre_checkpoint, post_checkpoint.
141+
142+
Pre-hooks receive the arguments dict. Post-hooks receive the result dict.
143+
"""
144+
if event not in self._hooks:
145+
raise ValueError(f"Unknown hook event: {event}. Valid: {list(self._hooks.keys())}")
146+
self._hooks[event].append(callback)
147+
148+
def _fire_hooks(self, event: str, data: Any) -> None:
149+
"""Fire all registered hooks for an event."""
150+
for callback in self._hooks.get(event, []):
151+
try:
152+
callback(data)
153+
except Exception:
154+
logger.debug("Hook %s failed", event)
155+
113156
# ------------------------------------------------------------------
114157
# Tool 1: remember
115158
# ------------------------------------------------------------------
@@ -126,6 +169,7 @@ def remember(
126169
checks for "remember to X when Y" patterns.
127170
"""
128171
uid = user_id or self._user_id
172+
self._fire_hooks("pre_remember", {"content": content, "user_id": uid, "metadata": metadata})
129173

130174
# Auto-tier memory content
131175
from dhee.core.session_tracker import classify_tier
@@ -157,6 +201,7 @@ def remember(
157201
if intention:
158202
response["detected_intention"] = intention.to_dict()
159203

204+
self._fire_hooks("post_remember", response)
160205
return response
161206

162207
# ------------------------------------------------------------------
@@ -171,6 +216,7 @@ def recall(
171216
) -> List[Dict[str, Any]]:
172217
"""Search memory for relevant facts. 0 LLM calls. 1 embedding."""
173218
uid = user_id or self._user_id
219+
self._fire_hooks("pre_recall", {"query": query, "user_id": uid, "limit": limit})
174220
results = self._engram.search(query, user_id=uid, limit=limit)
175221
formatted = [
176222
{
@@ -185,6 +231,7 @@ def recall(
185231
signals = self._tracker.on_recall(query, formatted)
186232
self._handle_tracker_signals(signals, uid)
187233

234+
self._fire_hooks("post_recall", formatted)
188235
return formatted
189236

190237
# ------------------------------------------------------------------
@@ -203,15 +250,21 @@ def context(
203250
operational: If True, return compact actionable-only format.
204251
"""
205252
uid = user_id or self._user_id
253+
self._fire_hooks("pre_context", {
254+
"task_description": task_description, "user_id": uid, "operational": operational,
255+
})
206256
self._tracker.on_context(task_description)
207257
hyper_ctx = self._buddhi.get_hyper_context(
208258
user_id=uid,
209259
task_description=task_description,
210260
memory=self._engram._memory,
211261
)
212262
if operational:
213-
return hyper_ctx.to_operational_dict()
214-
return hyper_ctx.to_dict()
263+
result = hyper_ctx.to_operational_dict()
264+
else:
265+
result = hyper_ctx.to_dict()
266+
self._fire_hooks("post_context", result)
267+
return result
215268

216269
# ------------------------------------------------------------------
217270
# Tool 4: checkpoint
@@ -253,6 +306,10 @@ def checkpoint(
253306
8. Selective forgetting → utility-based cleanup
254307
"""
255308
uid = user_id or self._user_id
309+
self._fire_hooks("pre_checkpoint", {
310+
"summary": summary, "user_id": uid, "task_type": task_type,
311+
"outcome_score": outcome_score, "status": status,
312+
})
256313
self._tracker.on_checkpoint()
257314

258315
# Auto-fill task_type if not provided
@@ -269,6 +326,7 @@ def checkpoint(
269326
what_worked = outcome.get("what_worked")
270327

271328
result: Dict[str, Any] = {}
329+
score = max(0.0, min(1.0, float(outcome_score))) if outcome_score is not None else None
272330

273331
# 1. Session digest
274332
try:
@@ -298,8 +356,7 @@ def checkpoint(
298356
pass
299357

300358
# 3. Outcome recording
301-
if task_type and outcome_score is not None:
302-
score = max(0.0, min(1.0, float(outcome_score)))
359+
if task_type and score is not None:
303360
insight = self._buddhi.record_outcome(
304361
user_id=uid, task_type=task_type, score=score,
305362
)
@@ -351,6 +408,7 @@ def checkpoint(
351408
forget_result = self._kernel.selective_forget(uid)
352409
result.update(forget_result)
353410

411+
self._fire_hooks("post_checkpoint", result)
354412
return result
355413

356414
# ------------------------------------------------------------------
@@ -433,6 +491,40 @@ def _handle_tracker_signals(self, signals: Dict[str, Any], user_id: str) -> None
433491
except Exception:
434492
pass
435493

494+
# ------------------------------------------------------------------
495+
# Cognition health (harness monitoring)
496+
# ------------------------------------------------------------------
497+
498+
def cognition_health(self, user_id: Optional[str] = None) -> Dict[str, Any]:
499+
"""Health status of all cognitive subsystems.
500+
501+
Returns counts, utility stats, and degradation warnings.
502+
Useful for harness dashboards and monitoring. Zero LLM calls.
503+
"""
504+
uid = user_id or self._user_id
505+
health: Dict[str, Any] = {}
506+
507+
health["kernel"] = self._kernel.get_stats()
508+
health["buddhi"] = self._buddhi.get_stats()
509+
510+
warnings: List[str] = []
511+
try:
512+
policies = self._kernel.policies.get_user_policies(uid)
513+
low_util = [p for p in policies if p.utility < -0.2 and p.apply_count >= 3]
514+
if low_util:
515+
warnings.append(f"{len(low_util)} policies with negative utility")
516+
active_intentions = self._kernel.intentions.get_active(uid)
517+
if len(active_intentions) > 20:
518+
warnings.append(f"{len(active_intentions)} active intentions (consider cleanup)")
519+
contradictions = self._kernel.beliefs.get_contradictions(uid)
520+
if len(contradictions) > 5:
521+
warnings.append(f"{len(contradictions)} unresolved belief contradictions")
522+
except Exception:
523+
pass
524+
525+
health["warnings"] = warnings
526+
return health
527+
436528
# ------------------------------------------------------------------
437529
# Phase 3: Belief management
438530
# ------------------------------------------------------------------

dhee/core/buddhi.py

Lines changed: 29 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,63 +1024,22 @@ def reflect(
10241024
except Exception:
10251025
pass
10261026

1027+
# Delegate cross-structure learning to kernel
1028+
# Kernel handles: policy outcomes, step extraction, belief-policy decay,
1029+
# intention feedback, episode connections
1030+
success = what_worked is not None
10271031
if what_worked:
1028-
try:
1029-
matched = self._kernel.policies.match_policies(
1030-
user_id, task_type, f"{task_type} task",
1031-
)
1032-
for policy in matched:
1033-
self._kernel.policies.record_outcome(
1034-
policy.id,
1035-
success=True,
1036-
baseline_score=baseline_score,
1037-
actual_score=outcome_score,
1038-
)
1039-
1040-
completed = self._kernel.tasks.get_tasks_by_type(
1041-
user_id, task_type, limit=10,
1042-
)
1043-
if len(completed) >= 3:
1044-
task_dicts = [t.to_dict() for t in completed]
1045-
self._kernel.policies.extract_from_tasks(
1046-
user_id, task_dicts, task_type,
1047-
)
1048-
# Step-level policy extraction from failure patterns
1049-
self._kernel.policies.extract_step_policies(
1050-
user_id, task_dicts, task_type,
1051-
)
1052-
except Exception:
1053-
pass
1054-
1032+
self._kernel.record_learning_outcomes(
1033+
user_id, task_type, success=True,
1034+
baseline_score=baseline_score, actual_score=outcome_score,
1035+
)
10551036
if what_failed:
1056-
try:
1057-
matched = self._kernel.policies.match_policies(
1058-
user_id, task_type, f"{task_type} task",
1059-
)
1060-
for policy in matched:
1061-
self._kernel.policies.record_outcome(
1062-
policy.id,
1063-
success=False,
1064-
baseline_score=baseline_score,
1065-
actual_score=outcome_score,
1066-
)
1067-
except Exception:
1068-
pass
1069-
1070-
# Extract step policies from failure patterns
1071-
try:
1072-
completed = self._kernel.tasks.get_tasks_by_type(
1073-
user_id, task_type, limit=10,
1074-
)
1075-
if len(completed) >= 3:
1076-
task_dicts = [t.to_dict() for t in completed]
1077-
self._kernel.policies.extract_step_policies(
1078-
user_id, task_dicts, task_type,
1079-
)
1080-
except Exception:
1081-
pass
1037+
self._kernel.record_learning_outcomes(
1038+
user_id, task_type, success=False,
1039+
baseline_score=baseline_score, actual_score=outcome_score,
1040+
)
10821041

1083-
# Update beliefs based on outcomes (via kernel)
1042+
# Update beliefs based on outcomes (buddhi-owned: text-based matching)
10841043
if what_worked:
10851044
try:
10861045
relevant = self._kernel.beliefs.get_relevant_beliefs(
@@ -1105,70 +1064,38 @@ def reflect(
11051064
except Exception:
11061065
pass
11071066

1108-
# Cross-structure: when beliefs are challenged, check dependent policies
1109-
try:
1110-
for belief in relevant:
1111-
if belief.confidence < 0.3:
1112-
claim_words = set(belief.claim.lower().split()[:5])
1113-
for policy in self._kernel.policies._policies.values():
1114-
if policy.user_id != user_id:
1115-
continue
1116-
approach_words = set(policy.action.approach.lower().split())
1117-
if len(claim_words & approach_words) >= 2:
1118-
policy.utility *= 0.8
1119-
policy.updated_at = time.time()
1120-
except Exception:
1121-
pass
1122-
1123-
# Record outcomes on matched insights (utility tracking)
1124-
if what_worked:
1125-
try:
1126-
matched_insights = self._get_relevant_insights(
1127-
user_id, f"{task_type} task",
1128-
)
1129-
for insight in matched_insights[:5]:
1130-
insight.record_outcome(
1131-
success=True,
1132-
baseline_score=baseline_score,
1133-
actual_score=outcome_score,
1134-
)
1135-
self._save_insights()
1136-
except Exception:
1137-
pass
1138-
1139-
if what_failed:
1140-
try:
1141-
matched_insights = self._get_relevant_insights(
1142-
user_id, f"{task_type} task",
1067+
# Buddhi-owned: insight utility tracking (buddhi owns insights)
1068+
try:
1069+
matched_insights = self._get_relevant_insights(
1070+
user_id, f"{task_type} task",
1071+
)
1072+
for insight in matched_insights[:5]:
1073+
insight.record_outcome(
1074+
success=success,
1075+
baseline_score=baseline_score,
1076+
actual_score=outcome_score,
11431077
)
1144-
for insight in matched_insights[:5]:
1145-
insight.record_outcome(
1146-
success=False,
1147-
baseline_score=baseline_score,
1148-
actual_score=outcome_score,
1149-
)
1150-
self._save_insights()
1151-
except Exception:
1152-
pass
1078+
self._save_insights()
1079+
except Exception:
1080+
pass
11531081

1154-
# Record outcomes on matched contrastive pairs
1082+
# Buddhi-owned: contrastive pair utility (buddhi owns contrastive store)
11551083
try:
11561084
store = self._get_contrastive()
11571085
matched_pairs = store.retrieve_contrasts(
11581086
f"{task_type} task", user_id=user_id, limit=5,
11591087
)
1160-
task_succeeded = what_worked is not None
11611088
for pair in matched_pairs:
11621089
pair.record_outcome(
1163-
success=task_succeeded,
1090+
success=success,
11641091
baseline_score=baseline_score,
11651092
actual_score=outcome_score,
11661093
)
11671094
store._save_all()
11681095
except Exception:
11691096
pass
11701097

1171-
# Cross-structure: positive policy delta -> reinforce related heuristics
1098+
# Buddhi-owned: heuristic reinforcement from positive policy deltas
11721099
if what_worked:
11731100
try:
11741101
matched_policies = self._kernel.policies.match_policies(
@@ -1190,26 +1117,6 @@ def reflect(
11901117
except Exception:
11911118
pass
11921119

1193-
# Record outcomes on recently triggered intentions
1194-
try:
1195-
triggered = [
1196-
i for i in self._kernel.intentions._intentions.values()
1197-
if i.user_id == user_id
1198-
and i.status == "triggered"
1199-
and i.was_useful is None
1200-
]
1201-
task_succeeded = what_worked is not None and (
1202-
outcome_score is None or outcome_score >= 0.5
1203-
)
1204-
for intention in triggered:
1205-
self._kernel.intentions.record_outcome(
1206-
intention.id,
1207-
useful=task_succeeded,
1208-
outcome_score=outcome_score,
1209-
)
1210-
except Exception:
1211-
pass
1212-
12131120
return new_insights
12141121

12151122
def _validate_used_heuristics(

0 commit comments

Comments
 (0)