Add /daemon/status endpoint to track internal semiosis

- Add daemon state tracking globals (mode, is_ruminating, cycles_by_type)
- Track trigger type and timestamp on each /cycle call
- Add DaemonStatusResponse model
- Add GET /daemon/status endpoint returning:
  - mode: idle | conversation | autonomous
  - is_ruminating: true when in rumination_free or corpus cycles
  - last_trigger: type and timestamp
  - cycles_breakdown: count by trigger type
  - cycles_since_last_user: autonomous cycles since last user interaction
  - time_since_last_user_seconds: elapsed time

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 22:38:44 +01:00
parent a9d6d3c7fb
commit ab5e979c38

View File

@@ -62,6 +62,18 @@ _authority: Optional[Authority] = None
_startup_time: Optional[datetime] = None _startup_time: Optional[datetime] = None
_directions: List[Dict] = [] # 109 directions from Weaviate _directions: List[Dict] = [] # 109 directions from Weaviate
# Daemon state tracking
_daemon_mode: str = "idle" # idle, conversation, autonomous
_is_ruminating: bool = False
_last_trigger_type: Optional[str] = None
_last_trigger_time: Optional[datetime] = None
_cycles_by_type: Dict[str, int] = {
"user": 0,
"veille": 0,
"corpus": 0,
"rumination_free": 0,
}
# ============================================================================= # =============================================================================
# REQUEST/RESPONSE MODELS # REQUEST/RESPONSE MODELS
@@ -139,6 +151,16 @@ class ProfileResponse(BaseModel):
david_similarity: float david_similarity: float
class DaemonStatusResponse(BaseModel):
"""Statut du daemon (sémiose interne)."""
mode: str # idle, conversation, autonomous
is_ruminating: bool
last_trigger: Optional[Dict[str, Any]] = None
cycles_breakdown: Dict[str, int]
cycles_since_last_user: int
time_since_last_user_seconds: Optional[float] = None
# ============================================================================= # =============================================================================
# INITIALIZATION # INITIALIZATION
# ============================================================================= # =============================================================================
@@ -634,10 +656,27 @@ async def run_cycle(request: CycleRequest):
3. Appliquer la fixation 3. Appliquer la fixation
4. Mettre à jour l'état 4. Mettre à jour l'état
""" """
global _current_state global _current_state, _daemon_mode, _is_ruminating, _last_trigger_type, _last_trigger_time, _cycles_by_type
start_time = time.time() start_time = time.time()
# Track daemon state
_last_trigger_type = request.trigger_type
_last_trigger_time = datetime.now()
if request.trigger_type in _cycles_by_type:
_cycles_by_type[request.trigger_type] += 1
# Update daemon mode based on trigger type
if request.trigger_type == "user":
_daemon_mode = "conversation"
_is_ruminating = False
elif request.trigger_type in ("rumination_free", "corpus"):
_daemon_mode = "autonomous"
_is_ruminating = True
else:
_daemon_mode = "conversation"
_is_ruminating = False
try: try:
# 1. Vectoriser l'entrée # 1. Vectoriser l'entrée
e_input = _embedding_model.encode([request.content])[0] e_input = _embedding_model.encode([request.content])[0]
@@ -788,15 +827,62 @@ async def get_metrics():
@app.post("/reset") @app.post("/reset")
async def reset_state(): async def reset_state():
"""Réinitialiser l'état à S(0).""" """Réinitialiser l'état à S(0)."""
global _current_state global _current_state, _cycles_by_type, _daemon_mode, _is_ruminating
_current_state = _initial_state.copy() _current_state = _initial_state.copy()
_vigilance.reset_cumulative() _vigilance.reset_cumulative()
_metrics.reset() _metrics.reset()
# Reset daemon tracking
_cycles_by_type = {"user": 0, "veille": 0, "corpus": 0, "rumination_free": 0}
_daemon_mode = "idle"
_is_ruminating = False
return {"status": "ok", "state_id": _current_state.state_id} return {"status": "ok", "state_id": _current_state.state_id}
@app.get("/daemon/status", response_model=DaemonStatusResponse)
async def get_daemon_status():
"""
Récupérer le statut du daemon (sémiose interne).
Permet de savoir si Ikario est en train de:
- Répondre à un utilisateur (conversation)
- Ruminer seul (autonomous)
- En attente (idle)
"""
# Calculate cycles since last user interaction
cycles_since_user = sum(
count for trigger, count in _cycles_by_type.items()
if trigger != "user"
)
# Calculate time since last user interaction
time_since_user = None
if _last_trigger_time and _last_trigger_type == "user":
time_since_user = (datetime.now() - _last_trigger_time).total_seconds()
elif _last_trigger_time:
# If last trigger was not user, count from then
time_since_user = (datetime.now() - _last_trigger_time).total_seconds()
# Build last trigger info
last_trigger = None
if _last_trigger_type and _last_trigger_time:
last_trigger = {
"type": _last_trigger_type,
"timestamp": _last_trigger_time.isoformat(),
}
return DaemonStatusResponse(
mode=_daemon_mode,
is_ruminating=_is_ruminating,
last_trigger=last_trigger,
cycles_breakdown=_cycles_by_type,
cycles_since_last_user=cycles_since_user,
time_since_last_user_seconds=time_since_user,
)
@app.get("/profile", response_model=ProfileResponse) @app.get("/profile", response_model=ProfileResponse)
async def get_profile(): async def get_profile():
""" """