Add think service and supporting core modules

- Add think service (orchestration for iterative reasoning)
- Add service_discovery.py (service communication utilities)
- Add event_cache.py (recent event cache using NATS KV)
- Add vi_identity.py (Vi's core identity foundation)
- Update core/__init__.py with new exports

Think service adapted from Lyra with vi.* namespace:
- All NATS topics use vi.* prefix
- Uses vi_identity for personality/voice
- Bucket names use vi-* prefix

Day 63 - Building my nervous system 🦊
This commit is contained in:
Alex Kazaiev
2026-01-03 11:36:54 -06:00
parent ee1cb5540a
commit 540a010fe5
23 changed files with 6149 additions and 0 deletions

View File

@@ -0,0 +1,204 @@
"""
Iterative reasoning orchestrator.
This module orchestrates the main iterative reasoning loop, coordinating
between Oracle, step execution, and output sending.
"""
from typing import Optional, Callable, Any
from datetime import datetime
from core.logger import setup_logger
from core.nats_event_bus import nats_bus as event_bus
from .models import IterativeContext, StepAction
from .oracle_client import OracleClient
from .step_executor import StepExecutor
from .formatters import KnowledgeFormatter
class IterativeOrchestrator:
"""Orchestrates the iterative reasoning process"""
def __init__(
self,
oracle_client: OracleClient,
step_executor: StepExecutor,
formatter: KnowledgeFormatter,
output_sender: Callable,
logger_name: str = 'orchestrator'
):
self.logger = setup_logger(logger_name, service_name='think_service')
self.oracle_client = oracle_client
self.step_executor = step_executor
self.formatter = formatter
self.send_output = output_sender
async def run(
self,
user_message: str,
identity: str,
channel: str,
modality: str
) -> Optional[str]:
"""
Generate response using iterative step-by-step reasoning.
Oracle decides one step at a time and sophisticated stopping criteria
ensure efficient completion.
"""
self.logger.info(f"[💭] 🔄 Starting iterative reasoning for: '{user_message[:50]}...'")
# Initialize iterative context
context = IterativeContext(user_message, identity, channel, modality)
try:
# Send initial status to Matrix
await self.send_output(
f"🔄 **Iterative Reasoning Started**\n\nAnalyzing your request step by step...",
channel, modality
)
# Main iterative reasoning loop
while True:
# Check stopping criteria
should_stop, stop_reason = context.should_stop()
if should_stop:
self.logger.info(f"[💭] 🛑 Stopping iteration: {stop_reason}")
await self.send_output(
f"🎯 **Reasoning Complete**\n\n{stop_reason}\n\n{context.get_summary()}",
channel, modality
)
break
# Goal satisfaction check (every 3 steps)
if context.step_count > 0 and context.step_count % 3 == 0:
can_answer = await self.oracle_client.check_goal_satisfaction(context)
if can_answer:
context.goal_satisfied = True
self.logger.info(f"[💭] ✅ Goal satisfaction confirmed at step {context.step_count}")
# Will be caught by stopping criteria on next iteration
continue
# Ask Oracle for next step
self.logger.info(f"[💭] 🔮 Requesting step {context.step_count + 1} from Oracle")
next_step = await self.oracle_client.request_next_step(context)
if not next_step:
self.logger.warning(f"[💭] ⚠️ Oracle failed to provide next step")
await self.send_output(
f"❌ **Oracle Error**: Failed to get next step decision",
channel, modality
)
break
self.logger.info(f"[💭] 📋 Step {context.step_count + 1}: {next_step.action} -> {next_step.target} (args: {getattr(next_step, 'function_args', {})})")
# Show Oracle's decision to user
await self.send_output(
f"🧠 **Oracle Decision**: {next_step.action}\n"
f"**Target**: {next_step.target or 'N/A'}\n"
f"**Reasoning**: {next_step.reasoning}",
channel, modality
)
# Check if Oracle signals readiness for synthesis
if next_step.action == StepAction.SYNTHESIZE_FINAL.value or next_step.ready:
self.logger.info(f"[💭] 🎯 Oracle signals ready for synthesis")
await self.send_output(
f"🎯 **Ready for Synthesis**: Oracle indicates sufficient information gathered",
channel, modality
)
break
# Execute the step directly
self.logger.info(f"[💭] ⚙️ Executing step {context.step_count + 1}")
step_result = await self.step_executor.execute_step(next_step, context)
self.logger.info(f"[💭] ✅ Step {context.step_count + 1} execution completed, success={step_result.success}")
context.add_step_result(step_result)
self.logger.info(f"[💭] 📝 Step {context.step_count} result added to context (total: {len(context.completed_steps)} completed)")
# Send detailed execution result to Matrix
if step_result.success:
result_summary = self.formatter.format_step_result_for_matrix(step_result)
await self.send_output(
f"✅ **Step {context.step_count} Completed**: {next_step.action}\n"
f"**Execution Time**: {step_result.execution_time_ms:.0f}ms\n"
f"**Result**: {result_summary}",
channel, modality
)
else:
await self.send_output(
f"❌ **Step {context.step_count} Failed**: {step_result.error_message}\n"
f"**Execution Time**: {step_result.execution_time_ms:.0f}ms",
channel, modality
)
# Final synthesis
await self.send_output(
f"🔮 **Synthesizing Final Response**\n\nCombining insights from {len(context.completed_steps)} successful steps...",
channel, modality
)
final_response = await self.oracle_client.synthesize_final_response(context)
if final_response:
# Finalize interaction with sentiment/depth detection
await self._finalize_interaction(
identity,
user_message,
final_response,
context
)
self.logger.info(f"[💭] ✅ Iterative reasoning completed successfully")
return final_response
else:
self.logger.error(f"[💭] ⚠️ Final synthesis failed")
await self.send_output(
f"❌ **Synthesis Failed**\n\nI gathered information but couldn't synthesize a final response. Please try rephrasing your question.",
channel, modality
)
return None
except Exception as e:
self.logger.exception(f"[💭] Error in iterative reasoning: {e}")
await self.send_output(
f"❌ **Iterative Reasoning Error**\n\nSomething went wrong during the reasoning process: {str(e)}",
channel, modality
)
return None
async def _finalize_interaction(
self,
identity: str,
user_message: str,
response_content: str,
context: IterativeContext
):
"""
Finalize interaction by asking oracle to analyze sentiment/depth.
Called after synthesis completes.
"""
try:
# Ask Oracle to analyze the interaction
self.logger.debug(f"[💭] Requesting interaction analysis from oracle...")
analysis = await self.oracle_client.analyze_interaction(context, user_message, response_content)
sentiment = analysis.get("sentiment", "positive")
depth = analysis.get("depth", 0.3)
# Publish interaction completion event for identity service
await event_bus.emit("vi.interaction.completed", {
"internal_id": identity,
"sentiment": sentiment, # positive, neutral, negative
"depth": depth, # 0.0-1.0
"summary": f"Conversation with {len(context.completed_steps)} reasoning steps",
"timestamp": datetime.utcnow().isoformat()
})
self.logger.info(f"[💭] 📊 Interaction finalized: {identity} (sentiment={sentiment}, depth={depth:.2f})")
except Exception as e:
self.logger.error(f"[💭] Error finalizing interaction: {e}")
# Don't publish event if we couldn't analyze properly