neuro-simulator 0.3.3__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. neuro_simulator/agent/core.py +73 -138
  2. neuro_simulator/agent/llm.py +1 -1
  3. neuro_simulator/agent/memory/manager.py +24 -103
  4. neuro_simulator/agent/tools/add_temp_memory.py +3 -3
  5. neuro_simulator/agent/tools/add_to_core_memory_block.py +2 -2
  6. neuro_simulator/agent/tools/create_core_memory_block.py +2 -2
  7. neuro_simulator/agent/tools/delete_core_memory_block.py +2 -2
  8. neuro_simulator/agent/tools/get_core_memory_block.py +2 -2
  9. neuro_simulator/agent/tools/get_core_memory_blocks.py +2 -2
  10. neuro_simulator/agent/tools/manager.py +86 -63
  11. neuro_simulator/agent/tools/remove_from_core_memory_block.py +2 -2
  12. neuro_simulator/agent/tools/speak.py +2 -2
  13. neuro_simulator/agent/tools/update_core_memory_block.py +2 -2
  14. neuro_simulator/api/system.py +5 -2
  15. neuro_simulator/cli.py +83 -53
  16. neuro_simulator/core/agent_factory.py +0 -1
  17. neuro_simulator/core/application.py +46 -32
  18. neuro_simulator/core/config.py +66 -63
  19. neuro_simulator/core/path_manager.py +69 -0
  20. neuro_simulator/services/audience.py +0 -2
  21. neuro_simulator/services/audio.py +0 -1
  22. neuro_simulator/services/builtin.py +10 -25
  23. neuro_simulator/services/letta.py +19 -1
  24. neuro_simulator/services/stream.py +24 -21
  25. neuro_simulator/utils/logging.py +9 -0
  26. neuro_simulator/utils/queue.py +27 -4
  27. neuro_simulator/utils/websocket.py +1 -3
  28. {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.0.dist-info}/METADATA +1 -1
  29. neuro_simulator-0.4.0.dist-info/RECORD +46 -0
  30. neuro_simulator/agent/base.py +0 -43
  31. neuro_simulator/agent/factory.py +0 -30
  32. neuro_simulator/api/stream.py +0 -1
  33. neuro_simulator-0.3.3.dist-info/RECORD +0 -48
  34. {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.0.dist-info}/WHEEL +0 -0
  35. {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.0.dist-info}/entry_points.txt +0 -0
  36. {neuro_simulator-0.3.3.dist-info → neuro_simulator-0.4.0.dist-info}/top_level.txt +0 -0
@@ -2,119 +2,142 @@
2
2
  """The central tool manager for the agent, responsible for loading, managing, and executing tools."""
3
3
 
4
4
  import os
5
+ import json
5
6
  import importlib
6
7
  import inspect
7
8
  import logging
9
+ import shutil
8
10
  from pathlib import Path
9
- from typing import Dict, Any, List
11
+ from typing import Any, Dict, List
10
12
 
11
13
  from .base import BaseTool
12
14
  from ..memory.manager import MemoryManager
15
+ from ...core.path_manager import path_manager
13
16
 
14
17
  logger = logging.getLogger(__name__.replace("neuro_simulator", "agent", 1))
15
18
 
16
19
  class ToolManager:
17
20
  """
18
21
  Acts as a central registry and executor for all available tools.
19
- This manager dynamically loads tools from the 'tools' directory, making the system extensible.
22
+ This manager dynamically loads tools from the user's working directory.
20
23
  """
21
24
 
22
25
  def __init__(self, memory_manager: MemoryManager):
23
- """
24
- Initializes the ToolManager.
25
- Args:
26
- memory_manager: An instance of MemoryManager, passed to tools that need it.
27
- """
26
+ if not path_manager:
27
+ raise RuntimeError("PathManager not initialized before ToolManager.")
28
28
  self.memory_manager = memory_manager
29
29
  self.tools: Dict[str, BaseTool] = {}
30
- self._load_and_register_tools()
30
+ self.agent_tool_allocations: Dict[str, List[str]] = {}
31
31
 
32
- # Hardcoded initial allocation of tool tags to agent types
33
- self.agent_tool_allocations = {
34
- "neuro_agent": ["communication", "memory_read"],
35
- "memory_agent": ["memory_write", "memory_read"]
36
- }
37
- logger.info(f"Initial tool allocations set: {self.agent_tool_allocations}")
32
+ self._copy_builtin_tools()
33
+ self.reload_tools() # Initial load
34
+ self._load_allocations()
35
+
36
+ def _copy_builtin_tools(self):
37
+ """Copies the packaged built-in tools to the working directory, overwriting existing ones."""
38
+ try:
39
+ import pkg_resources
40
+ source_dir_str = pkg_resources.resource_filename('neuro_simulator', 'agent/tools')
41
+ source_dir = Path(source_dir_str)
42
+ except (ModuleNotFoundError, KeyError):
43
+ source_dir = Path(__file__).parent
44
+
45
+ dest_dir = path_manager.builtin_tools_dir
46
+ if not dest_dir.exists():
47
+ os.makedirs(dest_dir)
48
+
49
+ for item in os.listdir(source_dir):
50
+ source_item = source_dir / item
51
+ if source_item.is_file() and source_item.name.endswith('.py') and not source_item.name.startswith(('__', 'manager')):
52
+ shutil.copy(source_item, dest_dir / item)
53
+ logger.info(f"Built-in tools copied to {dest_dir}")
38
54
 
39
55
  def _load_and_register_tools(self):
40
- """Dynamically scans the 'tools' directory, imports modules, and registers tool instances."""
56
+ """Dynamically scans the user tools directory, imports modules, and registers tool instances."""
41
57
  self.tools = {}
42
- tools_dir = Path(__file__).parent
43
- package_name = self.__module__.rsplit('.', 1)[0]
44
-
45
- for filename in os.listdir(tools_dir):
46
- if filename.endswith('.py') and not filename.startswith(('__', 'base', 'manager')):
47
- module_name = f".{filename[:-3]}"
48
- try:
49
- module = importlib.import_module(module_name, package=package_name)
50
- for _, cls in inspect.getmembers(module, inspect.isclass):
51
- if issubclass(cls, BaseTool) and cls is not BaseTool:
52
- tool_instance = cls(memory_manager=self.memory_manager)
53
- if tool_instance.name in self.tools:
54
- logger.warning(f"Duplicate tool name '{tool_instance.name}' found. Overwriting.")
55
- self.tools[tool_instance.name] = tool_instance
56
- logger.info(f"Successfully loaded and registered tool: {tool_instance.name}")
57
- except Exception as e:
58
- logger.error(f"Failed to load tool from {filename}: {e}", exc_info=True)
58
+ tools_dir = path_manager.user_tools_dir
59
+
60
+ for root, _, files in os.walk(tools_dir):
61
+ for filename in files:
62
+ if filename.endswith('.py') and not filename.startswith('__'):
63
+ module_path = Path(root) / filename
64
+ # Create a module spec from the file path
65
+ spec = importlib.util.spec_from_file_location(module_path.stem, module_path)
66
+ if spec:
67
+ try:
68
+ module = importlib.util.module_from_spec(spec)
69
+ spec.loader.exec_module(module)
70
+ for _, cls in inspect.getmembers(module, inspect.isclass):
71
+ if issubclass(cls, BaseTool) and cls is not BaseTool:
72
+ tool_instance = cls(memory_manager=self.memory_manager)
73
+ if tool_instance.name in self.tools:
74
+ logger.warning(f"Duplicate tool name '{tool_instance.name}' found. Overwriting.")
75
+ self.tools[tool_instance.name] = tool_instance
76
+ logger.info(f"Successfully loaded and registered tool: {tool_instance.name}")
77
+ except Exception as e:
78
+ logger.error(f"Failed to load tool from {module_path}: {e}", exc_info=True)
79
+
80
+ def _load_allocations(self):
81
+ """Loads tool allocations from JSON files, creating defaults if they don't exist."""
82
+ default_allocations = {
83
+ "neuro_agent": ["speak", "get_core_memory_blocks", "get_core_memory_block"],
84
+ "memory_agent": ["add_temp_memory", "create_core_memory_block", "update_core_memory_block", "delete_core_memory_block", "add_to_core_memory_block", "remove_from_core_memory_block", "get_core_memory_blocks", "get_core_memory_block"]
85
+ }
86
+
87
+ # Load neuro agent allocations
88
+ if path_manager.neuro_tools_path.exists():
89
+ with open(path_manager.neuro_tools_path, 'r', encoding='utf-8') as f:
90
+ self.agent_tool_allocations['neuro_agent'] = json.load(f)
91
+ else:
92
+ self.agent_tool_allocations['neuro_agent'] = default_allocations['neuro_agent']
93
+ with open(path_manager.neuro_tools_path, 'w', encoding='utf-8') as f:
94
+ json.dump(default_allocations['neuro_agent'], f, indent=2)
95
+
96
+ # Load memory agent allocations
97
+ if path_manager.memory_agent_tools_path.exists():
98
+ with open(path_manager.memory_agent_tools_path, 'r', encoding='utf-8') as f:
99
+ self.agent_tool_allocations['memory_agent'] = json.load(f)
100
+ else:
101
+ self.agent_tool_allocations['memory_agent'] = default_allocations['memory_agent']
102
+ with open(path_manager.memory_agent_tools_path, 'w', encoding='utf-8') as f:
103
+ json.dump(default_allocations['memory_agent'], f, indent=2)
104
+
105
+ logger.info(f"Tool allocations loaded: {self.agent_tool_allocations}")
59
106
 
60
107
  def get_all_tool_schemas(self) -> List[Dict[str, Any]]:
61
- """
62
- Returns a list of serializable schemas for all registered tools.
63
- This is the 'API documentation' for the agent.
64
- """
65
108
  return [tool.get_schema() for tool in self.tools.values()]
66
109
 
67
110
  def get_tool_schemas_for_agent(self, agent_name: str) -> List[Dict[str, Any]]:
68
- """
69
- Returns a list of tool schemas available to a specific agent
70
- based on the configured name allocations.
71
- """
72
111
  allowed_names = set(self.agent_tool_allocations.get(agent_name, []))
73
112
  if not allowed_names:
74
113
  return []
75
-
76
114
  return [tool.get_schema() for tool in self.tools.values() if tool.name in allowed_names]
77
115
 
78
116
  def reload_tools(self):
79
- """Forces a re-scan and registration of tools from the tools directory."""
80
117
  logger.info("Reloading tools...")
81
118
  self._load_and_register_tools()
82
119
  logger.info(f"Tools reloaded. {len(self.tools)} tools available.")
83
120
 
84
121
  def get_allocations(self) -> Dict[str, List[str]]:
85
- """Returns the current agent-to-tool-tag allocation mapping."""
86
122
  return self.agent_tool_allocations
87
123
 
88
124
  def set_allocations(self, allocations: Dict[str, List[str]]):
89
- """
90
- Sets a new agent-to-tool allocation mapping.
91
-
92
- Args:
93
- allocations: A dictionary mapping agent names to lists of tool names.
94
- """
95
- # Basic validation can be added here if needed
96
125
  self.agent_tool_allocations = allocations
97
- logger.info(f"Tool allocations updated: {self.agent_tool_allocations}")
126
+ # Persist the changes to the JSON files
127
+ with open(path_manager.neuro_tools_path, 'w', encoding='utf-8') as f:
128
+ json.dump(allocations.get('neuro_agent', []), f, indent=2)
129
+ with open(path_manager.memory_agent_tools_path, 'w', encoding='utf-8') as f:
130
+ json.dump(allocations.get('memory_agent', []), f, indent=2)
131
+ logger.info(f"Tool allocations updated and saved: {self.agent_tool_allocations}")
98
132
 
99
133
  async def execute_tool(self, tool_name: str, **kwargs: Any) -> Dict[str, Any]:
100
- """
101
- Executes a tool by its name with the given parameters.
102
-
103
- Args:
104
- tool_name: The name of the tool to execute.
105
- **kwargs: The parameters to pass to the tool's execute method.
106
-
107
- Returns:
108
- A JSON-serializable dictionary with the execution result.
109
- """
110
134
  if tool_name not in self.tools:
111
135
  logger.error(f"Attempted to execute non-existent tool: {tool_name}")
112
136
  return {"error": f"Tool '{tool_name}' not found."}
113
-
114
137
  tool = self.tools[tool_name]
115
138
  try:
116
139
  result = await tool.execute(**kwargs)
117
140
  return result
118
141
  except Exception as e:
119
142
  logger.error(f"Error executing tool '{tool_name}' with params {kwargs}: {e}", exc_info=True)
120
- return {"error": f"An unexpected error occurred while executing the tool: {str(e)}"}
143
+ return {"error": f"An unexpected error occurred while executing the tool: {str(e)}"}
@@ -3,8 +3,8 @@
3
3
 
4
4
  from typing import Dict, Any, List
5
5
 
6
- from .base import BaseTool
7
- from ..memory.manager import MemoryManager
6
+ from neuro_simulator.agent.tools.base import BaseTool
7
+ from neuro_simulator.agent.memory.manager import MemoryManager
8
8
 
9
9
  class RemoveFromCoreMemoryBlockTool(BaseTool):
10
10
  """Tool to remove an item from an existing core memory block's content list by its index."""
@@ -4,8 +4,8 @@
4
4
  import logging
5
5
  from typing import Dict, Any, List
6
6
 
7
- from .base import BaseTool
8
- from ..memory.manager import MemoryManager
7
+ from neuro_simulator.agent.tools.base import BaseTool
8
+ from neuro_simulator.agent.memory.manager import MemoryManager
9
9
 
10
10
  logger = logging.getLogger(__name__.replace("neuro_simulator", "agent", 1))
11
11
 
@@ -3,8 +3,8 @@
3
3
 
4
4
  from typing import Dict, Any, List, Optional
5
5
 
6
- from .base import BaseTool
7
- from ..memory.manager import MemoryManager
6
+ from neuro_simulator.agent.tools.base import BaseTool
7
+ from neuro_simulator.agent.memory.manager import MemoryManager
8
8
 
9
9
  class UpdateCoreMemoryBlockTool(BaseTool):
10
10
  """Tool to update an existing core memory block."""
@@ -1,10 +1,13 @@
1
1
  # neuro_simulator/api/system.py
2
2
  """API endpoints for system, config, and utility functions."""
3
3
 
4
- from fastapi import APIRouter, Depends, HTTPException, status, Request
5
4
  import time
5
+ from typing import Any, Dict
6
+
7
+ from fastapi import APIRouter, Depends, HTTPException, status, Request
6
8
 
7
- from ..core.config import config_manager
9
+ from ..core.config import AppSettings, config_manager
10
+ from ..utils.process import process_manager
8
11
 
9
12
 
10
13
  router = APIRouter(tags=["System & Utilities"])
neuro_simulator/cli.py CHANGED
@@ -8,29 +8,7 @@ import shutil
8
8
  import sys
9
9
  from pathlib import Path
10
10
 
11
- def copy_resource(package_name: str, resource_path: str, destination_path: Path, is_dir: bool = False):
12
- """A helper function to copy a resource from the package to the working directory."""
13
- if destination_path.exists():
14
- return
15
11
 
16
- try:
17
- import pkg_resources
18
- source_path_str = pkg_resources.resource_filename(package_name, resource_path)
19
- source_path = Path(source_path_str)
20
- except (ModuleNotFoundError, KeyError):
21
- source_path = Path(__file__).parent / resource_path
22
-
23
- if source_path.exists():
24
- try:
25
- if is_dir:
26
- shutil.copytree(source_path, destination_path)
27
- else:
28
- shutil.copy(source_path, destination_path)
29
- logging.info(f"Created '{destination_path}' from package resource.")
30
- except Exception as e:
31
- logging.warning(f"Could not copy resource '{resource_path}'. Error: {e}")
32
- else:
33
- logging.warning(f"Resource '{resource_path}' not found in package or development folder.")
34
12
 
35
13
  def main():
36
14
  """Main entry point for the CLI."""
@@ -54,39 +32,91 @@ def main():
54
32
  os.chdir(work_dir)
55
33
  logging.info(f"Using working directory: {work_dir}")
56
34
 
57
- # 2. Ensure required assets and configs exist
58
- copy_resource('neuro_simulator', 'core/config.yaml.example', work_dir / 'config.yaml.example')
59
- copy_resource('neuro_simulator', 'assets', work_dir / 'assets', is_dir=True)
60
- # Ensure agent directory and its contents exist
61
- agent_dir = work_dir / "agent"
62
- agent_dir.mkdir(parents=True, exist_ok=True)
63
- copy_resource('neuro_simulator', 'agent/neuro_prompt.txt', agent_dir / 'neuro_prompt.txt')
64
- copy_resource('neuro_simulator', 'agent/memory_prompt.txt', agent_dir / 'memory_prompt.txt')
65
-
66
- # Ensure agent memory directory and its contents exist
67
- agent_memory_dir = agent_dir / "memory"
68
- agent_memory_dir.mkdir(parents=True, exist_ok=True)
69
- for filename in ["chat_history.json", "core_memory.json", "init_memory.json"]:
70
- copy_resource('neuro_simulator', f'agent/memory/{filename}', agent_memory_dir / filename)
71
-
72
- # 3. Validate essential files
73
- errors = []
74
- if not (work_dir / "config.yaml").exists():
75
- errors.append(f"'config.yaml' not found in '{work_dir}'. Please copy 'config.yaml.example' to 'config.yaml' and configure it.")
76
- if not (work_dir / "assets" / "neuro_start.mp4").exists():
77
- errors.append(f"Required file 'neuro_start.mp4' not found in '{work_dir / 'assets'}'.")
78
-
79
- if errors:
80
- for error in errors:
81
- logging.error(error)
82
- sys.exit(1)
35
+ # 2. Initialize paths and load configuration
36
+ from neuro_simulator.core import path_manager
37
+ from neuro_simulator.core.config import config_manager
38
+ import uvicorn
39
+
40
+ path_manager.initialize_path_manager(os.getcwd())
41
+
42
+ # Define example_path early for config loading
43
+ example_path = Path(__file__).parent / "core" / "config.yaml.example"
44
+
45
+ # 2.2. Copy default config.yaml.example if it doesn't exist
46
+ try:
47
+ source_config_example = example_path
48
+ destination_config_example = path_manager.path_manager.working_dir / "config.yaml.example"
49
+ if not destination_config_example.exists():
50
+ shutil.copy(source_config_example, destination_config_example)
51
+ logging.info(f"Copied default config.yaml.example to {destination_config_example}")
52
+ except Exception as e:
53
+ logging.warning(f"Could not copy default config.yaml.example: {e}")
54
+
55
+ main_config_path = path_manager.path_manager.working_dir / "config.yaml"
56
+ config_manager.load_and_validate(str(main_config_path), str(example_path))
57
+
58
+ # 2.5. Copy default prompt templates if they don't exist
59
+ try:
60
+ # Use Path(__file__).parent for robust path resolution
61
+ base_path = Path(__file__).parent
62
+ neuro_prompt_example = base_path / "agent" / "neuro_prompt.txt"
63
+ memory_prompt_example = base_path / "agent" / "memory_prompt.txt"
64
+
65
+ if not path_manager.path_manager.neuro_prompt_path.exists():
66
+ shutil.copy(neuro_prompt_example, path_manager.path_manager.neuro_prompt_path)
67
+ logging.info(f"Copied default neuro prompt to {path_manager.path_manager.neuro_prompt_path}")
68
+ if not path_manager.path_manager.memory_agent_prompt_path.exists():
69
+ shutil.copy(memory_prompt_example, path_manager.path_manager.memory_agent_prompt_path)
70
+ logging.info(f"Copied default memory prompt to {path_manager.path_manager.memory_agent_prompt_path}")
71
+
72
+ # Copy default memory JSON files if they don't exist
73
+ memory_files = {
74
+ "core_memory.json": path_manager.path_manager.core_memory_path,
75
+ "init_memory.json": path_manager.path_manager.init_memory_path,
76
+ "temp_memory.json": path_manager.path_manager.temp_memory_path,
77
+ }
78
+ for filename, dest_path in memory_files.items():
79
+ src_path = base_path / "agent" / "memory" / filename
80
+ if not dest_path.exists():
81
+ shutil.copy(src_path, dest_path)
82
+ logging.info(f"Copied default {filename} to {dest_path}")
83
+
84
+ # Copy default assets directory if it doesn't exist
85
+ source_assets_dir = base_path / "assets"
86
+ destination_assets_dir = path_manager.path_manager.assets_dir
87
+
88
+ # Ensure the destination assets directory exists
89
+ destination_assets_dir.mkdir(parents=True, exist_ok=True)
90
+
91
+ # Copy individual files from source assets to destination assets
92
+ for item in source_assets_dir.iterdir():
93
+ if item.is_file():
94
+ dest_file = destination_assets_dir / item.name
95
+ if not dest_file.exists():
96
+ shutil.copy(item, dest_file)
97
+ logging.info(f"Copied asset {item.name} to {dest_file}")
98
+ elif item.is_dir():
99
+ # Recursively copy subdirectories if they don't exist
100
+ dest_subdir = destination_assets_dir / item.name
101
+ if not dest_subdir.exists():
102
+ shutil.copytree(item, dest_subdir)
103
+ logging.info(f"Copied asset directory {item.name} to {dest_subdir}")
104
+ except Exception as e:
105
+ logging.warning(f"Could not copy default prompt templates, memory files, or assets: {e}")
106
+
107
+ # 3. Determine server host and port
108
+ server_host = args.host or config_manager.settings.server.host
109
+ server_port = args.port or config_manager.settings.server.port
83
110
 
84
- # 4. Import and run the server
111
+ # 4. Run the server
112
+ logging.info(f"Starting Neuro-Simulator server on {server_host}:{server_port}...")
85
113
  try:
86
- from neuro_simulator.core.application import run_server
87
- logging.info("Starting Neuro-Simulator server...")
88
- # The full application logger will take over from here
89
- run_server(args.host, args.port)
114
+ uvicorn.run(
115
+ "neuro_simulator.core.application:app",
116
+ host=server_host,
117
+ port=server_port,
118
+ reload=False
119
+ )
90
120
  except ImportError as e:
91
121
  logging.error(f"Could not import the application. Make sure the package is installed correctly. Details: {e}", exc_info=True)
92
122
  sys.exit(1)
@@ -1,6 +1,5 @@
1
1
  # neuro_simulator/core/agent_factory.py
2
2
  import logging
3
- from pathlib import Path
4
3
 
5
4
  from .agent_interface import BaseAgent
6
5
  from .config import config_manager, AppSettings
@@ -7,7 +7,7 @@ import logging
7
7
  import random
8
8
  import re
9
9
  import time
10
- from pathlib import Path
10
+ import os
11
11
  from typing import List
12
12
 
13
13
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect
@@ -17,6 +17,7 @@ from starlette.websockets import WebSocketState
17
17
  # --- Core Imports ---
18
18
  from .config import config_manager, AppSettings
19
19
  from ..core.agent_factory import create_agent
20
+ from ..agent.core import Agent as LocalAgent
20
21
  from ..services.letta import LettaAgent
21
22
  from ..services.builtin import BuiltinAgentWrapper
22
23
 
@@ -34,7 +35,8 @@ from ..utils.queue import (
34
35
  add_to_neuro_input_queue,
35
36
  get_recent_audience_chats,
36
37
  is_neuro_input_queue_empty,
37
- get_all_neuro_input_chats
38
+ get_all_neuro_input_chats,
39
+ initialize_queues,
38
40
  )
39
41
  from ..utils.state import app_state
40
42
  from ..utils.websocket import connection_manager
@@ -169,6 +171,10 @@ async def neuro_response_cycle():
169
171
  if not response_text:
170
172
  continue
171
173
 
174
+ # Push updated agent context to admin clients immediately after processing
175
+ updated_context = await agent.get_message_history()
176
+ await connection_manager.broadcast_to_admins({"type": "agent_context", "action": "update", "messages": updated_context})
177
+
172
178
  async with app_state.neuro_last_speech_lock:
173
179
  app_state.neuro_last_speech = response_text
174
180
 
@@ -192,6 +198,7 @@ async def neuro_response_cycle():
192
198
 
193
199
  await connection_manager.broadcast({"type": "neuro_speech_segment", "is_end": True})
194
200
  live_stream_manager.set_neuro_speaking_status(False)
201
+
195
202
  await asyncio.sleep(config_manager.settings.neuro_behavior.post_speech_cooldown_sec)
196
203
 
197
204
  except asyncio.TimeoutError:
@@ -210,17 +217,24 @@ async def neuro_response_cycle():
210
217
  @app.on_event("startup")
211
218
  async def startup_event():
212
219
  """Actions to perform on application startup."""
213
- global chatbot_manager
220
+ # 1. Configure logging first
214
221
  configure_server_logging()
215
-
222
+
223
+ # 2. Initialize queues now that config is loaded
224
+ initialize_queues()
225
+
226
+ # 4. Initialize other managers/services that depend on config
227
+ global chatbot_manager
216
228
  chatbot_manager = AudienceChatbotManager()
217
229
 
230
+ # 5. Register callbacks
218
231
  async def metadata_callback(settings: AppSettings):
219
232
  await live_stream_manager.broadcast_stream_metadata()
220
233
 
221
234
  config_manager.register_update_callback(metadata_callback)
222
235
  config_manager.register_update_callback(chatbot_manager.handle_config_update)
223
236
 
237
+ # 6. Initialize agent (which will load its own configs)
224
238
  try:
225
239
  await create_agent()
226
240
  logger.info(f"Successfully initialized agent type: {config_manager.settings.agent_type}")
@@ -410,6 +424,8 @@ async def handle_admin_ws_message(websocket: WebSocket, data: dict):
410
424
 
411
425
  # Stream Control Actions
412
426
  elif action == "start_stream":
427
+ logger.info("Start stream action received. Resetting agent memory before starting processes...")
428
+ await agent.reset_memory()
413
429
  if not process_manager.is_running:
414
430
  process_manager.start_live_processes()
415
431
  response["payload"] = {"status": "success", "message": "Stream started"}
@@ -464,22 +480,33 @@ async def handle_admin_ws_message(websocket: WebSocket, data: dict):
464
480
  response["payload"] = context
465
481
 
466
482
  elif action == "get_last_prompt":
467
- # Check if the agent supports prompt generation introspection
468
- agent_instance = getattr(agent, 'agent_instance', None) if hasattr(agent, 'agent_instance') else agent
469
- if not hasattr(agent_instance, 'memory_manager') or not hasattr(agent_instance.memory_manager, 'get_recent_chat') or not hasattr(agent_instance, '_build_neuro_prompt'):
470
- response["payload"] = {"status": "error", "message": "The active agent does not support prompt generation introspection."}
483
+ # This is specific to the builtin agent, as Letta doesn't expose its prompt.
484
+ agent_instance = getattr(agent, 'agent_instance', agent)
485
+ if not isinstance(agent_instance, LocalAgent):
486
+ response["payload"] = {"prompt": "The active agent does not support prompt generation introspection."}
471
487
  else:
472
- recent_history = await agent_instance.memory_manager.get_recent_chat(entries=10)
473
- messages_for_prompt = []
474
- for entry in recent_history:
475
- if entry.get('role') == 'user':
476
- parts = entry.get('content', '').split(':', 1)
477
- if len(parts) == 2:
478
- messages_for_prompt.append({'username': parts[0].strip(), 'text': parts[1].strip()})
479
- else:
480
- messages_for_prompt.append({'username': 'user', 'text': entry.get('content', '')})
481
- prompt = await agent_instance._build_neuro_prompt(messages_for_prompt)
482
- response["payload"] = {"prompt": prompt}
488
+ try:
489
+ # 1. Get the recent history from the agent itself
490
+ history = await agent_instance.get_neuro_history(limit=10)
491
+
492
+ # 2. Reconstruct the 'messages' list that _build_neuro_prompt expects
493
+ messages_for_prompt = []
494
+ for entry in history:
495
+ if entry.get('role') == 'user':
496
+ # Content is in the format "username: text"
497
+ content = entry.get('content', '')
498
+ parts = content.split(':', 1)
499
+ if len(parts) == 2:
500
+ messages_for_prompt.append({'username': parts[0].strip(), 'text': parts[1].strip()})
501
+ elif content: # Handle cases where there's no colon
502
+ messages_for_prompt.append({'username': 'user', 'text': content})
503
+
504
+ # 3. Build the prompt using the agent's own internal logic
505
+ prompt = await agent_instance._build_neuro_prompt(messages_for_prompt)
506
+ response["payload"] = {"prompt": prompt}
507
+ except Exception as e:
508
+ logger.error(f"Error generating last prompt: {e}", exc_info=True)
509
+ response["payload"] = {"prompt": f"Failed to generate prompt: {e}"}
483
510
 
484
511
  elif action == "reset_agent_memory":
485
512
  await agent.reset_memory()
@@ -504,17 +531,4 @@ async def handle_admin_ws_message(websocket: WebSocket, data: dict):
504
531
  await websocket.send_json(response)
505
532
 
506
533
 
507
- # --- Server Entrypoint ---
508
534
 
509
- def run_server(host: str = None, port: int = None):
510
- """Runs the FastAPI server with Uvicorn."""
511
- import uvicorn
512
- server_host = host or config_manager.settings.server.host
513
- server_port = port or config_manager.settings.server.port
514
-
515
- uvicorn.run(
516
- "neuro_simulator.core.application:app",
517
- host=server_host,
518
- port=server_port,
519
- reload=False
520
- )