From f821e95d3c738a04a1b23406157a9f66f2b6fbb6 Mon Sep 17 00:00:00 2001 From: Re-bin Date: Sat, 14 Feb 2026 01:40:37 +0000 Subject: [PATCH] fix: wire max_tokens/temperature to all chat calls, clean up redundant comments --- README.md | 2 +- nanobot/agent/loop.py | 62 ++++++++------------------------------ nanobot/agent/subagent.py | 6 ++++ nanobot/cli/commands.py | 4 ++- nanobot/session/manager.py | 28 ++++------------- 5 files changed, 28 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index aeb6bfc..e73beb5 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ ⚡️ Delivers core agent functionality in just **~4,000** lines of code — **99% smaller** than Clawdbot's 430k+ lines. -📏 Real-time line count: **3,587 lines** (run `bash core_agent_lines.sh` to verify anytime) +📏 Real-time line count: **3,536 lines** (run `bash core_agent_lines.sh` to verify anytime) ## 📢 News diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index 091d205..c256a56 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -2,7 +2,6 @@ import asyncio import json -from datetime import datetime from pathlib import Path from typing import Any @@ -44,6 +43,7 @@ class AgentLoop: model: str | None = None, max_iterations: int = 20, temperature: float = 0.7, + max_tokens: int = 4096, memory_window: int = 50, brave_api_key: str | None = None, exec_config: "ExecToolConfig | None" = None, @@ -59,6 +59,7 @@ class AgentLoop: self.model = model or provider.get_default_model() self.max_iterations = max_iterations self.temperature = temperature + self.max_tokens = max_tokens self.memory_window = memory_window self.brave_api_key = brave_api_key self.exec_config = exec_config or ExecToolConfig() @@ -66,8 +67,6 @@ class AgentLoop: self.restrict_to_workspace = restrict_to_workspace self.context = ContextBuilder(workspace) - - # Initialize session manager self.sessions = session_manager or SessionManager(workspace) self.tools = ToolRegistry() self.subagents = SubagentManager( @@ -75,6 +74,8 @@ class AgentLoop: workspace=workspace, bus=bus, model=self.model, + temperature=self.temperature, + max_tokens=self.max_tokens, brave_api_key=brave_api_key, exec_config=self.exec_config, restrict_to_workspace=restrict_to_workspace, @@ -152,6 +153,7 @@ class AgentLoop: tools=self.tools.get_definitions(), model=self.model, temperature=self.temperature, + max_tokens=self.max_tokens, ) if response.has_tool_calls: @@ -193,20 +195,16 @@ class AgentLoop: while self._running: try: - # Wait for next message msg = await asyncio.wait_for( self.bus.consume_inbound(), timeout=1.0 ) - - # Process it try: response = await self._process_message(msg) if response: await self.bus.publish_outbound(response) except Exception as e: logger.error(f"Error processing message: {e}") - # Send error response await self.bus.publish_outbound(OutboundMessage( channel=msg.channel, chat_id=msg.chat_id, @@ -231,15 +229,13 @@ class AgentLoop: Returns: The response message, or None if no response needed. """ - # Handle system messages (subagent announces) - # The chat_id contains the original "channel:chat_id" to route back to + # System messages route back via chat_id ("channel:chat_id") if msg.channel == "system": return await self._process_system_message(msg) preview = msg.content[:80] + "..." if len(msg.content) > 80 else msg.content logger.info(f"Processing message from {msg.channel}:{msg.sender_id}: {preview}") - # Get or create session key = session_key or msg.session_key session = self.sessions.get_or_create(key) @@ -250,12 +246,9 @@ class AgentLoop: messages_to_archive = session.messages.copy() session.clear() self.sessions.save(session) - # Clear cache to force reload from disk on next request - self.sessions._cache.pop(session.key, None) + self.sessions.invalidate(session.key) - # Consolidate in background (non-blocking) async def _consolidate_and_cleanup(): - # Create a temporary session with archived messages temp_session = Session(key=session.key) temp_session.messages = messages_to_archive await self._consolidate_memory(temp_session, archive_all=True) @@ -267,34 +260,25 @@ class AgentLoop: return OutboundMessage(channel=msg.channel, chat_id=msg.chat_id, content="🐈 nanobot commands:\n/new — Start a new conversation\n/help — Show available commands") - # Consolidate memory before processing if session is too large - # Run in background to avoid blocking main conversation if len(session.messages) > self.memory_window: asyncio.create_task(self._consolidate_memory(session)) - # Update tool contexts self._set_tool_context(msg.channel, msg.chat_id) - - # Build initial messages initial_messages = self.context.build_messages( - history=session.get_history(), + history=session.get_history(max_messages=self.memory_window), current_message=msg.content, media=msg.media if msg.media else None, channel=msg.channel, chat_id=msg.chat_id, ) - - # Run agent loop final_content, tools_used = await self._run_agent_loop(initial_messages) if final_content is None: final_content = "I've completed processing but have no response to give." - # Log response preview preview = final_content[:120] + "..." if len(final_content) > 120 else final_content logger.info(f"Response to {msg.channel}:{msg.sender_id}: {preview}") - # Save to session (include tool names so consolidation sees what happened) session.add_message("user", msg.content) session.add_message("assistant", final_content, tools_used=tools_used if tools_used else None) @@ -326,28 +310,20 @@ class AgentLoop: origin_channel = "cli" origin_chat_id = msg.chat_id - # Use the origin session for context session_key = f"{origin_channel}:{origin_chat_id}" session = self.sessions.get_or_create(session_key) - - # Update tool contexts self._set_tool_context(origin_channel, origin_chat_id) - - # Build messages with the announce content initial_messages = self.context.build_messages( - history=session.get_history(), + history=session.get_history(max_messages=self.memory_window), current_message=msg.content, channel=origin_channel, chat_id=origin_chat_id, ) - - # Run agent loop final_content, _ = await self._run_agent_loop(initial_messages) if final_content is None: final_content = "Background task completed." - # Save to session (mark as system message in history) session.add_message("user", f"[System: {msg.sender_id}] {msg.content}") session.add_message("assistant", final_content) self.sessions.save(session) @@ -367,33 +343,26 @@ class AgentLoop: """ memory = MemoryStore(self.workspace) - # Handle /new command: clear session and consolidate everything if archive_all: - old_messages = session.messages # All messages - keep_count = 0 # Clear everything + old_messages = session.messages + keep_count = 0 logger.info(f"Memory consolidation (archive_all): {len(session.messages)} total messages archived") else: - # Normal consolidation: only write files, keep session intact keep_count = self.memory_window // 2 - - # Check if consolidation is needed if len(session.messages) <= keep_count: logger.debug(f"Session {session.key}: No consolidation needed (messages={len(session.messages)}, keep={keep_count})") return - # Use last_consolidated to avoid re-processing messages messages_to_process = len(session.messages) - session.last_consolidated if messages_to_process <= 0: logger.debug(f"Session {session.key}: No new messages to consolidate (last_consolidated={session.last_consolidated}, total={len(session.messages)})") return - # Get messages to consolidate (from last_consolidated to keep_count from end) old_messages = session.messages[session.last_consolidated:-keep_count] if not old_messages: return logger.info(f"Memory consolidation started: {len(session.messages)} total, {len(old_messages)} new to consolidate, {keep_count} keep") - # Format messages for LLM (include tool names when available) lines = [] for m in old_messages: if not m.get("content"): @@ -436,18 +405,11 @@ Respond with ONLY valid JSON, no markdown fences.""" if update != current_memory: memory.write_long_term(update) - # Update last_consolidated to track what's been processed if archive_all: - # /new command: reset to 0 after clearing session.last_consolidated = 0 else: - # Normal: mark up to (total - keep_count) as consolidated session.last_consolidated = len(session.messages) - keep_count - - # Key: We do NOT modify session.messages (append-only for cache) - # The consolidation is only for human-readable files (MEMORY.md/HISTORY.md) - # LLM cache remains intact because the messages list is unchanged - logger.info(f"Memory consolidation done: {len(session.messages)} total messages (unchanged), last_consolidated={session.last_consolidated}") + logger.info(f"Memory consolidation done: {len(session.messages)} messages, last_consolidated={session.last_consolidated}") except Exception as e: logger.error(f"Memory consolidation failed: {e}") diff --git a/nanobot/agent/subagent.py b/nanobot/agent/subagent.py index 9e0cd7c..203836a 100644 --- a/nanobot/agent/subagent.py +++ b/nanobot/agent/subagent.py @@ -32,6 +32,8 @@ class SubagentManager: workspace: Path, bus: MessageBus, model: str | None = None, + temperature: float = 0.7, + max_tokens: int = 4096, brave_api_key: str | None = None, exec_config: "ExecToolConfig | None" = None, restrict_to_workspace: bool = False, @@ -41,6 +43,8 @@ class SubagentManager: self.workspace = workspace self.bus = bus self.model = model or provider.get_default_model() + self.temperature = temperature + self.max_tokens = max_tokens self.brave_api_key = brave_api_key self.exec_config = exec_config or ExecToolConfig() self.restrict_to_workspace = restrict_to_workspace @@ -130,6 +134,8 @@ class SubagentManager: messages=messages, tools=tools.get_definitions(), model=self.model, + temperature=self.temperature, + max_tokens=self.max_tokens, ) if response.has_tool_calls: diff --git a/nanobot/cli/commands.py b/nanobot/cli/commands.py index daaaa97..17210ce 100644 --- a/nanobot/cli/commands.py +++ b/nanobot/cli/commands.py @@ -338,6 +338,7 @@ def gateway( workspace=config.workspace_path, model=config.agents.defaults.model, temperature=config.agents.defaults.temperature, + max_tokens=config.agents.defaults.max_tokens, max_iterations=config.agents.defaults.max_tool_iterations, memory_window=config.agents.defaults.memory_window, brave_api_key=config.tools.web.search.api_key or None, @@ -445,8 +446,9 @@ def agent( provider=provider, workspace=config.workspace_path, model=config.agents.defaults.model, - max_iterations=config.agents.defaults.max_tool_iterations, temperature=config.agents.defaults.temperature, + max_tokens=config.agents.defaults.max_tokens, + max_iterations=config.agents.defaults.max_tool_iterations, memory_window=config.agents.defaults.memory_window, brave_api_key=config.tools.web.search.api_key or None, exec_config=config.tools.exec, diff --git a/nanobot/session/manager.py b/nanobot/session/manager.py index 9fce9ee..bce12a1 100644 --- a/nanobot/session/manager.py +++ b/nanobot/session/manager.py @@ -42,23 +42,8 @@ class Session: self.updated_at = datetime.now() def get_history(self, max_messages: int = 500) -> list[dict[str, Any]]: - """ - Get message history for LLM context. - - Messages are returned in append-only order for cache efficiency. - Only the most recent max_messages are returned, but the order - is always stable for the same max_messages value. - - Args: - max_messages: Maximum messages to return (most recent). - - Returns: - List of messages in LLM format (role and content only). - """ - recent = self.messages[-max_messages:] - - # Convert to LLM format (just role and content) - return [{"role": m["role"], "content": m["content"]} for m in recent] + """Get recent messages in LLM format (role + content only).""" + return [{"role": m["role"], "content": m["content"]} for m in self.messages[-max_messages:]] def clear(self) -> None: """Clear all messages and reset session to initial state.""" @@ -94,11 +79,9 @@ class SessionManager: Returns: The session. """ - # Check cache if key in self._cache: return self._cache[key] - # Try to load from disk session = self._load(key) if session is None: session = Session(key=key) @@ -150,7 +133,6 @@ class SessionManager: path = self._get_session_path(session.key) with open(path, "w") as f: - # Write metadata first metadata_line = { "_type": "metadata", "created_at": session.created_at.isoformat(), @@ -159,13 +141,15 @@ class SessionManager: "last_consolidated": session.last_consolidated } f.write(json.dumps(metadata_line) + "\n") - - # Write messages for msg in session.messages: f.write(json.dumps(msg) + "\n") self._cache[session.key] = session + def invalidate(self, key: str) -> None: + """Remove a session from the in-memory cache.""" + self._cache.pop(key, None) + def list_sessions(self) -> list[dict[str, Any]]: """ List all sessions.