@@ -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 # ------------------------------------------------------------------
0 commit comments