fix: wire max_tokens/temperature to all chat calls, clean up redundant comments
This commit is contained in:
parent
155fc48b29
commit
f821e95d3c
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
⚡️ Delivers core agent functionality in just **~4,000** lines of code — **99% smaller** than Clawdbot's 430k+ lines.
|
⚡️ 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
|
## 📢 News
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -44,6 +43,7 @@ class AgentLoop:
|
|||||||
model: str | None = None,
|
model: str | None = None,
|
||||||
max_iterations: int = 20,
|
max_iterations: int = 20,
|
||||||
temperature: float = 0.7,
|
temperature: float = 0.7,
|
||||||
|
max_tokens: int = 4096,
|
||||||
memory_window: int = 50,
|
memory_window: int = 50,
|
||||||
brave_api_key: str | None = None,
|
brave_api_key: str | None = None,
|
||||||
exec_config: "ExecToolConfig | None" = None,
|
exec_config: "ExecToolConfig | None" = None,
|
||||||
@ -59,6 +59,7 @@ class AgentLoop:
|
|||||||
self.model = model or provider.get_default_model()
|
self.model = model or provider.get_default_model()
|
||||||
self.max_iterations = max_iterations
|
self.max_iterations = max_iterations
|
||||||
self.temperature = temperature
|
self.temperature = temperature
|
||||||
|
self.max_tokens = max_tokens
|
||||||
self.memory_window = memory_window
|
self.memory_window = memory_window
|
||||||
self.brave_api_key = brave_api_key
|
self.brave_api_key = brave_api_key
|
||||||
self.exec_config = exec_config or ExecToolConfig()
|
self.exec_config = exec_config or ExecToolConfig()
|
||||||
@ -66,8 +67,6 @@ class AgentLoop:
|
|||||||
self.restrict_to_workspace = restrict_to_workspace
|
self.restrict_to_workspace = restrict_to_workspace
|
||||||
|
|
||||||
self.context = ContextBuilder(workspace)
|
self.context = ContextBuilder(workspace)
|
||||||
|
|
||||||
# Initialize session manager
|
|
||||||
self.sessions = session_manager or SessionManager(workspace)
|
self.sessions = session_manager or SessionManager(workspace)
|
||||||
self.tools = ToolRegistry()
|
self.tools = ToolRegistry()
|
||||||
self.subagents = SubagentManager(
|
self.subagents = SubagentManager(
|
||||||
@ -75,6 +74,8 @@ class AgentLoop:
|
|||||||
workspace=workspace,
|
workspace=workspace,
|
||||||
bus=bus,
|
bus=bus,
|
||||||
model=self.model,
|
model=self.model,
|
||||||
|
temperature=self.temperature,
|
||||||
|
max_tokens=self.max_tokens,
|
||||||
brave_api_key=brave_api_key,
|
brave_api_key=brave_api_key,
|
||||||
exec_config=self.exec_config,
|
exec_config=self.exec_config,
|
||||||
restrict_to_workspace=restrict_to_workspace,
|
restrict_to_workspace=restrict_to_workspace,
|
||||||
@ -152,6 +153,7 @@ class AgentLoop:
|
|||||||
tools=self.tools.get_definitions(),
|
tools=self.tools.get_definitions(),
|
||||||
model=self.model,
|
model=self.model,
|
||||||
temperature=self.temperature,
|
temperature=self.temperature,
|
||||||
|
max_tokens=self.max_tokens,
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.has_tool_calls:
|
if response.has_tool_calls:
|
||||||
@ -193,20 +195,16 @@ class AgentLoop:
|
|||||||
|
|
||||||
while self._running:
|
while self._running:
|
||||||
try:
|
try:
|
||||||
# Wait for next message
|
|
||||||
msg = await asyncio.wait_for(
|
msg = await asyncio.wait_for(
|
||||||
self.bus.consume_inbound(),
|
self.bus.consume_inbound(),
|
||||||
timeout=1.0
|
timeout=1.0
|
||||||
)
|
)
|
||||||
|
|
||||||
# Process it
|
|
||||||
try:
|
try:
|
||||||
response = await self._process_message(msg)
|
response = await self._process_message(msg)
|
||||||
if response:
|
if response:
|
||||||
await self.bus.publish_outbound(response)
|
await self.bus.publish_outbound(response)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error processing message: {e}")
|
logger.error(f"Error processing message: {e}")
|
||||||
# Send error response
|
|
||||||
await self.bus.publish_outbound(OutboundMessage(
|
await self.bus.publish_outbound(OutboundMessage(
|
||||||
channel=msg.channel,
|
channel=msg.channel,
|
||||||
chat_id=msg.chat_id,
|
chat_id=msg.chat_id,
|
||||||
@ -231,15 +229,13 @@ class AgentLoop:
|
|||||||
Returns:
|
Returns:
|
||||||
The response message, or None if no response needed.
|
The response message, or None if no response needed.
|
||||||
"""
|
"""
|
||||||
# Handle system messages (subagent announces)
|
# System messages route back via chat_id ("channel:chat_id")
|
||||||
# The chat_id contains the original "channel:chat_id" to route back to
|
|
||||||
if msg.channel == "system":
|
if msg.channel == "system":
|
||||||
return await self._process_system_message(msg)
|
return await self._process_system_message(msg)
|
||||||
|
|
||||||
preview = msg.content[:80] + "..." if len(msg.content) > 80 else msg.content
|
preview = msg.content[:80] + "..." if len(msg.content) > 80 else msg.content
|
||||||
logger.info(f"Processing message from {msg.channel}:{msg.sender_id}: {preview}")
|
logger.info(f"Processing message from {msg.channel}:{msg.sender_id}: {preview}")
|
||||||
|
|
||||||
# Get or create session
|
|
||||||
key = session_key or msg.session_key
|
key = session_key or msg.session_key
|
||||||
session = self.sessions.get_or_create(key)
|
session = self.sessions.get_or_create(key)
|
||||||
|
|
||||||
@ -250,12 +246,9 @@ class AgentLoop:
|
|||||||
messages_to_archive = session.messages.copy()
|
messages_to_archive = session.messages.copy()
|
||||||
session.clear()
|
session.clear()
|
||||||
self.sessions.save(session)
|
self.sessions.save(session)
|
||||||
# Clear cache to force reload from disk on next request
|
self.sessions.invalidate(session.key)
|
||||||
self.sessions._cache.pop(session.key, None)
|
|
||||||
|
|
||||||
# Consolidate in background (non-blocking)
|
|
||||||
async def _consolidate_and_cleanup():
|
async def _consolidate_and_cleanup():
|
||||||
# Create a temporary session with archived messages
|
|
||||||
temp_session = Session(key=session.key)
|
temp_session = Session(key=session.key)
|
||||||
temp_session.messages = messages_to_archive
|
temp_session.messages = messages_to_archive
|
||||||
await self._consolidate_memory(temp_session, archive_all=True)
|
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,
|
return OutboundMessage(channel=msg.channel, chat_id=msg.chat_id,
|
||||||
content="🐈 nanobot commands:\n/new — Start a new conversation\n/help — Show available commands")
|
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:
|
if len(session.messages) > self.memory_window:
|
||||||
asyncio.create_task(self._consolidate_memory(session))
|
asyncio.create_task(self._consolidate_memory(session))
|
||||||
|
|
||||||
# Update tool contexts
|
|
||||||
self._set_tool_context(msg.channel, msg.chat_id)
|
self._set_tool_context(msg.channel, msg.chat_id)
|
||||||
|
|
||||||
# Build initial messages
|
|
||||||
initial_messages = self.context.build_messages(
|
initial_messages = self.context.build_messages(
|
||||||
history=session.get_history(),
|
history=session.get_history(max_messages=self.memory_window),
|
||||||
current_message=msg.content,
|
current_message=msg.content,
|
||||||
media=msg.media if msg.media else None,
|
media=msg.media if msg.media else None,
|
||||||
channel=msg.channel,
|
channel=msg.channel,
|
||||||
chat_id=msg.chat_id,
|
chat_id=msg.chat_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Run agent loop
|
|
||||||
final_content, tools_used = await self._run_agent_loop(initial_messages)
|
final_content, tools_used = await self._run_agent_loop(initial_messages)
|
||||||
|
|
||||||
if final_content is None:
|
if final_content is None:
|
||||||
final_content = "I've completed processing but have no response to give."
|
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
|
preview = final_content[:120] + "..." if len(final_content) > 120 else final_content
|
||||||
logger.info(f"Response to {msg.channel}:{msg.sender_id}: {preview}")
|
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("user", msg.content)
|
||||||
session.add_message("assistant", final_content,
|
session.add_message("assistant", final_content,
|
||||||
tools_used=tools_used if tools_used else None)
|
tools_used=tools_used if tools_used else None)
|
||||||
@ -326,28 +310,20 @@ class AgentLoop:
|
|||||||
origin_channel = "cli"
|
origin_channel = "cli"
|
||||||
origin_chat_id = msg.chat_id
|
origin_chat_id = msg.chat_id
|
||||||
|
|
||||||
# Use the origin session for context
|
|
||||||
session_key = f"{origin_channel}:{origin_chat_id}"
|
session_key = f"{origin_channel}:{origin_chat_id}"
|
||||||
session = self.sessions.get_or_create(session_key)
|
session = self.sessions.get_or_create(session_key)
|
||||||
|
|
||||||
# Update tool contexts
|
|
||||||
self._set_tool_context(origin_channel, origin_chat_id)
|
self._set_tool_context(origin_channel, origin_chat_id)
|
||||||
|
|
||||||
# Build messages with the announce content
|
|
||||||
initial_messages = self.context.build_messages(
|
initial_messages = self.context.build_messages(
|
||||||
history=session.get_history(),
|
history=session.get_history(max_messages=self.memory_window),
|
||||||
current_message=msg.content,
|
current_message=msg.content,
|
||||||
channel=origin_channel,
|
channel=origin_channel,
|
||||||
chat_id=origin_chat_id,
|
chat_id=origin_chat_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Run agent loop
|
|
||||||
final_content, _ = await self._run_agent_loop(initial_messages)
|
final_content, _ = await self._run_agent_loop(initial_messages)
|
||||||
|
|
||||||
if final_content is None:
|
if final_content is None:
|
||||||
final_content = "Background task completed."
|
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("user", f"[System: {msg.sender_id}] {msg.content}")
|
||||||
session.add_message("assistant", final_content)
|
session.add_message("assistant", final_content)
|
||||||
self.sessions.save(session)
|
self.sessions.save(session)
|
||||||
@ -367,33 +343,26 @@ class AgentLoop:
|
|||||||
"""
|
"""
|
||||||
memory = MemoryStore(self.workspace)
|
memory = MemoryStore(self.workspace)
|
||||||
|
|
||||||
# Handle /new command: clear session and consolidate everything
|
|
||||||
if archive_all:
|
if archive_all:
|
||||||
old_messages = session.messages # All messages
|
old_messages = session.messages
|
||||||
keep_count = 0 # Clear everything
|
keep_count = 0
|
||||||
logger.info(f"Memory consolidation (archive_all): {len(session.messages)} total messages archived")
|
logger.info(f"Memory consolidation (archive_all): {len(session.messages)} total messages archived")
|
||||||
else:
|
else:
|
||||||
# Normal consolidation: only write files, keep session intact
|
|
||||||
keep_count = self.memory_window // 2
|
keep_count = self.memory_window // 2
|
||||||
|
|
||||||
# Check if consolidation is needed
|
|
||||||
if len(session.messages) <= keep_count:
|
if len(session.messages) <= keep_count:
|
||||||
logger.debug(f"Session {session.key}: No consolidation needed (messages={len(session.messages)}, keep={keep_count})")
|
logger.debug(f"Session {session.key}: No consolidation needed (messages={len(session.messages)}, keep={keep_count})")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Use last_consolidated to avoid re-processing messages
|
|
||||||
messages_to_process = len(session.messages) - session.last_consolidated
|
messages_to_process = len(session.messages) - session.last_consolidated
|
||||||
if messages_to_process <= 0:
|
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)})")
|
logger.debug(f"Session {session.key}: No new messages to consolidate (last_consolidated={session.last_consolidated}, total={len(session.messages)})")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get messages to consolidate (from last_consolidated to keep_count from end)
|
|
||||||
old_messages = session.messages[session.last_consolidated:-keep_count]
|
old_messages = session.messages[session.last_consolidated:-keep_count]
|
||||||
if not old_messages:
|
if not old_messages:
|
||||||
return
|
return
|
||||||
logger.info(f"Memory consolidation started: {len(session.messages)} total, {len(old_messages)} new to consolidate, {keep_count} keep")
|
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 = []
|
lines = []
|
||||||
for m in old_messages:
|
for m in old_messages:
|
||||||
if not m.get("content"):
|
if not m.get("content"):
|
||||||
@ -436,18 +405,11 @@ Respond with ONLY valid JSON, no markdown fences."""
|
|||||||
if update != current_memory:
|
if update != current_memory:
|
||||||
memory.write_long_term(update)
|
memory.write_long_term(update)
|
||||||
|
|
||||||
# Update last_consolidated to track what's been processed
|
|
||||||
if archive_all:
|
if archive_all:
|
||||||
# /new command: reset to 0 after clearing
|
|
||||||
session.last_consolidated = 0
|
session.last_consolidated = 0
|
||||||
else:
|
else:
|
||||||
# Normal: mark up to (total - keep_count) as consolidated
|
|
||||||
session.last_consolidated = len(session.messages) - keep_count
|
session.last_consolidated = len(session.messages) - keep_count
|
||||||
|
logger.info(f"Memory consolidation done: {len(session.messages)} messages, last_consolidated={session.last_consolidated}")
|
||||||
# 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}")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Memory consolidation failed: {e}")
|
logger.error(f"Memory consolidation failed: {e}")
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,8 @@ class SubagentManager:
|
|||||||
workspace: Path,
|
workspace: Path,
|
||||||
bus: MessageBus,
|
bus: MessageBus,
|
||||||
model: str | None = None,
|
model: str | None = None,
|
||||||
|
temperature: float = 0.7,
|
||||||
|
max_tokens: int = 4096,
|
||||||
brave_api_key: str | None = None,
|
brave_api_key: str | None = None,
|
||||||
exec_config: "ExecToolConfig | None" = None,
|
exec_config: "ExecToolConfig | None" = None,
|
||||||
restrict_to_workspace: bool = False,
|
restrict_to_workspace: bool = False,
|
||||||
@ -41,6 +43,8 @@ class SubagentManager:
|
|||||||
self.workspace = workspace
|
self.workspace = workspace
|
||||||
self.bus = bus
|
self.bus = bus
|
||||||
self.model = model or provider.get_default_model()
|
self.model = model or provider.get_default_model()
|
||||||
|
self.temperature = temperature
|
||||||
|
self.max_tokens = max_tokens
|
||||||
self.brave_api_key = brave_api_key
|
self.brave_api_key = brave_api_key
|
||||||
self.exec_config = exec_config or ExecToolConfig()
|
self.exec_config = exec_config or ExecToolConfig()
|
||||||
self.restrict_to_workspace = restrict_to_workspace
|
self.restrict_to_workspace = restrict_to_workspace
|
||||||
@ -130,6 +134,8 @@ class SubagentManager:
|
|||||||
messages=messages,
|
messages=messages,
|
||||||
tools=tools.get_definitions(),
|
tools=tools.get_definitions(),
|
||||||
model=self.model,
|
model=self.model,
|
||||||
|
temperature=self.temperature,
|
||||||
|
max_tokens=self.max_tokens,
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.has_tool_calls:
|
if response.has_tool_calls:
|
||||||
|
|||||||
@ -338,6 +338,7 @@ def gateway(
|
|||||||
workspace=config.workspace_path,
|
workspace=config.workspace_path,
|
||||||
model=config.agents.defaults.model,
|
model=config.agents.defaults.model,
|
||||||
temperature=config.agents.defaults.temperature,
|
temperature=config.agents.defaults.temperature,
|
||||||
|
max_tokens=config.agents.defaults.max_tokens,
|
||||||
max_iterations=config.agents.defaults.max_tool_iterations,
|
max_iterations=config.agents.defaults.max_tool_iterations,
|
||||||
memory_window=config.agents.defaults.memory_window,
|
memory_window=config.agents.defaults.memory_window,
|
||||||
brave_api_key=config.tools.web.search.api_key or None,
|
brave_api_key=config.tools.web.search.api_key or None,
|
||||||
@ -445,8 +446,9 @@ def agent(
|
|||||||
provider=provider,
|
provider=provider,
|
||||||
workspace=config.workspace_path,
|
workspace=config.workspace_path,
|
||||||
model=config.agents.defaults.model,
|
model=config.agents.defaults.model,
|
||||||
max_iterations=config.agents.defaults.max_tool_iterations,
|
|
||||||
temperature=config.agents.defaults.temperature,
|
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,
|
memory_window=config.agents.defaults.memory_window,
|
||||||
brave_api_key=config.tools.web.search.api_key or None,
|
brave_api_key=config.tools.web.search.api_key or None,
|
||||||
exec_config=config.tools.exec,
|
exec_config=config.tools.exec,
|
||||||
|
|||||||
@ -42,23 +42,8 @@ class Session:
|
|||||||
self.updated_at = datetime.now()
|
self.updated_at = datetime.now()
|
||||||
|
|
||||||
def get_history(self, max_messages: int = 500) -> list[dict[str, Any]]:
|
def get_history(self, max_messages: int = 500) -> list[dict[str, Any]]:
|
||||||
"""
|
"""Get recent messages in LLM format (role + content only)."""
|
||||||
Get message history for LLM context.
|
return [{"role": m["role"], "content": m["content"]} for m in self.messages[-max_messages:]]
|
||||||
|
|
||||||
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]
|
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
"""Clear all messages and reset session to initial state."""
|
"""Clear all messages and reset session to initial state."""
|
||||||
@ -94,11 +79,9 @@ class SessionManager:
|
|||||||
Returns:
|
Returns:
|
||||||
The session.
|
The session.
|
||||||
"""
|
"""
|
||||||
# Check cache
|
|
||||||
if key in self._cache:
|
if key in self._cache:
|
||||||
return self._cache[key]
|
return self._cache[key]
|
||||||
|
|
||||||
# Try to load from disk
|
|
||||||
session = self._load(key)
|
session = self._load(key)
|
||||||
if session is None:
|
if session is None:
|
||||||
session = Session(key=key)
|
session = Session(key=key)
|
||||||
@ -150,7 +133,6 @@ class SessionManager:
|
|||||||
path = self._get_session_path(session.key)
|
path = self._get_session_path(session.key)
|
||||||
|
|
||||||
with open(path, "w") as f:
|
with open(path, "w") as f:
|
||||||
# Write metadata first
|
|
||||||
metadata_line = {
|
metadata_line = {
|
||||||
"_type": "metadata",
|
"_type": "metadata",
|
||||||
"created_at": session.created_at.isoformat(),
|
"created_at": session.created_at.isoformat(),
|
||||||
@ -159,13 +141,15 @@ class SessionManager:
|
|||||||
"last_consolidated": session.last_consolidated
|
"last_consolidated": session.last_consolidated
|
||||||
}
|
}
|
||||||
f.write(json.dumps(metadata_line) + "\n")
|
f.write(json.dumps(metadata_line) + "\n")
|
||||||
|
|
||||||
# Write messages
|
|
||||||
for msg in session.messages:
|
for msg in session.messages:
|
||||||
f.write(json.dumps(msg) + "\n")
|
f.write(json.dumps(msg) + "\n")
|
||||||
|
|
||||||
self._cache[session.key] = session
|
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]]:
|
def list_sessions(self) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
List all sessions.
|
List all sessions.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user