Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions nanobot/agent/loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,53 @@ def __init__(
self.commands = CommandRouter()
register_builtin_commands(self.commands)

@classmethod
def from_config(
cls,
config: Any,
bus: MessageBus | None = None,
**extra: Any,
) -> AgentLoop:
"""Create an AgentLoop from config with the common parameter set.

Extra keyword arguments are forwarded to ``AgentLoop.__init__``,
allowing callers to override or extend the standard config-derived
parameters (e.g. ``cron_service``, ``session_manager``).
"""
from nanobot.providers.factory import make_provider

if bus is None:
bus = MessageBus()
defaults = config.agents.defaults
provider = extra.pop("provider", None) or make_provider(config)
model = extra.pop("model", None) or defaults.model
context_window_tokens = extra.pop("context_window_tokens", None) or defaults.context_window_tokens
return cls(
bus=bus,
provider=provider,
workspace=config.workspace_path,
model=model,
max_iterations=defaults.max_tool_iterations,
context_window_tokens=context_window_tokens,
context_block_limit=defaults.context_block_limit,
max_tool_result_chars=defaults.max_tool_result_chars,
provider_retry_mode=defaults.provider_retry_mode,
tool_hint_max_length=defaults.tool_hint_max_length,
web_config=config.tools.web,
exec_config=config.tools.exec,
restrict_to_workspace=config.tools.restrict_to_workspace,
mcp_servers=config.tools.mcp_servers,
channels_config=config.channels,
timezone=defaults.timezone,
unified_session=defaults.unified_session,
disabled_skills=defaults.disabled_skills,
session_ttl_minutes=defaults.session_ttl_minutes,
consolidation_ratio=defaults.consolidation_ratio,
max_messages=defaults.max_messages,
tools_config=config.tools,
**extra,
)

def _sync_subagent_runtime_limits(self) -> None:
"""Keep subagent runtime limits aligned with mutable loop settings."""
self.subagents.max_iterations = self.max_iterations
Expand Down
123 changes: 26 additions & 97 deletions nanobot/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from rich.text import Text

from nanobot import __logo__, __version__
from nanobot.agent.loop import AgentLoop


def _sanitize_surrogates(text: str) -> str:
Expand Down Expand Up @@ -447,20 +448,6 @@ def _onboard_plugins(config_path: Path) -> None:
json.dump(data, f, indent=2, ensure_ascii=False)


def _make_provider(config: Config):
"""Create the appropriate LLM provider from config.

Routing is driven by ``ProviderSpec.backend`` in the registry.
"""
from nanobot.providers.factory import make_provider

try:
return make_provider(config)
except ValueError as exc:
console.print(f"[red]Error: {exc}[/red]")
raise typer.Exit(1) from exc


def _load_runtime_config(config: str | None = None, workspace: str | None = None) -> Config:
"""Load config and optionally override the active workspace."""
from nanobot.config.loader import load_config, resolve_config_env_vars, set_config_path
Expand Down Expand Up @@ -539,7 +526,6 @@ def serve(

from loguru import logger

from nanobot.agent.loop import AgentLoop
from nanobot.api.server import create_app
from nanobot.bus.queue import MessageBus
from nanobot.session.manager import SessionManager
Expand All @@ -556,37 +542,19 @@ def serve(
timeout = timeout if timeout is not None else api_cfg.timeout
sync_workspace_templates(runtime_config.workspace_path)
bus = MessageBus()
provider = _make_provider(runtime_config)
session_manager = SessionManager(runtime_config.workspace_path)
agent_loop = AgentLoop(
bus=bus,
provider=provider,
workspace=runtime_config.workspace_path,
model=runtime_config.agents.defaults.model,
max_iterations=runtime_config.agents.defaults.max_tool_iterations,
context_window_tokens=runtime_config.agents.defaults.context_window_tokens,
context_block_limit=runtime_config.agents.defaults.context_block_limit,
max_tool_result_chars=runtime_config.agents.defaults.max_tool_result_chars,
provider_retry_mode=runtime_config.agents.defaults.provider_retry_mode,
tool_hint_max_length=runtime_config.agents.defaults.tool_hint_max_length,
web_config=runtime_config.tools.web,
exec_config=runtime_config.tools.exec,
restrict_to_workspace=runtime_config.tools.restrict_to_workspace,
session_manager=session_manager,
mcp_servers=runtime_config.tools.mcp_servers,
channels_config=runtime_config.channels,
timezone=runtime_config.agents.defaults.timezone,
unified_session=runtime_config.agents.defaults.unified_session,
disabled_skills=runtime_config.agents.defaults.disabled_skills,
session_ttl_minutes=runtime_config.agents.defaults.session_ttl_minutes,
consolidation_ratio=runtime_config.agents.defaults.consolidation_ratio,
max_messages=runtime_config.agents.defaults.max_messages,
tools_config=runtime_config.tools,
image_generation_provider_configs={
"openrouter": runtime_config.providers.openrouter,
"aihubmix": runtime_config.providers.aihubmix,
},
)
try:
agent_loop = AgentLoop.from_config(
runtime_config, bus,
session_manager=session_manager,
image_generation_provider_configs={
"openrouter": runtime_config.providers.openrouter,
"aihubmix": runtime_config.providers.aihubmix,
},
)
except ValueError as exc:
console.print(f"[red]Error: {exc}[/red]")
raise typer.Exit(1) from exc

model_name = runtime_config.agents.defaults.model
console.print(f"{__logo__} Starting OpenAI-compatible API server")
Expand Down Expand Up @@ -653,7 +621,6 @@ def _run_gateway(
open_browser_url: str | None = None,
) -> None:
"""Shared gateway runtime; ``open_browser_url`` opens a tab once channels are up."""
from nanobot.agent.loop import AgentLoop
from nanobot.agent.tools.cron import CronTool
from nanobot.agent.tools.message import MessageTool
from nanobot.bus.queue import MessageBus
Expand All @@ -674,7 +641,6 @@ def _run_gateway(
except ValueError as exc:
console.print(f"[red]Error: {exc}[/red]")
raise typer.Exit(1) from exc
provider = provider_snapshot.provider
session_manager = SessionManager(config.workspace_path)

# Preserve existing single-workspace installs, but keep custom workspaces clean.
Expand All @@ -686,31 +652,13 @@ def _run_gateway(
cron = CronService(cron_store_path)

# Create agent with cron service
agent = AgentLoop(
bus=bus,
provider=provider,
workspace=config.workspace_path,
agent = AgentLoop.from_config(
config, bus,
provider=provider_snapshot.provider,
model=provider_snapshot.model,
max_iterations=config.agents.defaults.max_tool_iterations,
context_window_tokens=provider_snapshot.context_window_tokens,
web_config=config.tools.web,
context_block_limit=config.agents.defaults.context_block_limit,
max_tool_result_chars=config.agents.defaults.max_tool_result_chars,
provider_retry_mode=config.agents.defaults.provider_retry_mode,
tool_hint_max_length=config.agents.defaults.tool_hint_max_length,
exec_config=config.tools.exec,
cron_service=cron,
restrict_to_workspace=config.tools.restrict_to_workspace,
session_manager=session_manager,
mcp_servers=config.tools.mcp_servers,
channels_config=config.channels,
timezone=config.agents.defaults.timezone,
unified_session=config.agents.defaults.unified_session,
disabled_skills=config.agents.defaults.disabled_skills,
session_ttl_minutes=config.agents.defaults.session_ttl_minutes,
consolidation_ratio=config.agents.defaults.consolidation_ratio,
max_messages=config.agents.defaults.max_messages,
tools_config=config.tools,
image_generation_provider_configs={
"openrouter": config.providers.openrouter,
"aihubmix": config.providers.aihubmix,
Expand Down Expand Up @@ -820,7 +768,7 @@ async def _silent(*_args, **_kwargs):

if job.payload.deliver and job.payload.to and response:
should_notify = await evaluate_response(
response, reminder_note, provider, agent.model,
response, reminder_note, agent.provider, agent.model,
)
if should_notify:
await _deliver_to_channel(
Expand Down Expand Up @@ -910,7 +858,7 @@ async def on_heartbeat_notify(response: str) -> None:
hb_cfg = config.gateway.heartbeat
heartbeat = HeartbeatService(
workspace=config.workspace_path,
provider=provider,
provider=agent.provider,
model=agent.model,
on_execute=on_heartbeat_execute,
on_notify=on_heartbeat_notify,
Expand Down Expand Up @@ -1063,15 +1011,13 @@ def agent(
"""Interact with the agent directly."""
from loguru import logger

from nanobot.agent.loop import AgentLoop
from nanobot.bus.queue import MessageBus
from nanobot.cron.service import CronService

config = _load_runtime_config(config, workspace)
sync_workspace_templates(config.workspace_path)

bus = MessageBus()
provider = _make_provider(config)

# Preserve existing single-workspace installs, but keep custom workspaces clean.
if is_default_workspace(config.workspace_path):
Expand All @@ -1086,31 +1032,14 @@ def agent(
else:
logger.disable("nanobot")

agent_loop = AgentLoop(
bus=bus,
provider=provider,
workspace=config.workspace_path,
model=config.agents.defaults.model,
max_iterations=config.agents.defaults.max_tool_iterations,
context_window_tokens=config.agents.defaults.context_window_tokens,
web_config=config.tools.web,
context_block_limit=config.agents.defaults.context_block_limit,
max_tool_result_chars=config.agents.defaults.max_tool_result_chars,
provider_retry_mode=config.agents.defaults.provider_retry_mode,
tool_hint_max_length=config.agents.defaults.tool_hint_max_length,
exec_config=config.tools.exec,
cron_service=cron,
restrict_to_workspace=config.tools.restrict_to_workspace,
mcp_servers=config.tools.mcp_servers,
channels_config=config.channels,
timezone=config.agents.defaults.timezone,
unified_session=config.agents.defaults.unified_session,
disabled_skills=config.agents.defaults.disabled_skills,
session_ttl_minutes=config.agents.defaults.session_ttl_minutes,
consolidation_ratio=config.agents.defaults.consolidation_ratio,
max_messages=config.agents.defaults.max_messages,
tools_config=config.tools,
)
try:
agent_loop = AgentLoop.from_config(
config, bus,
cron_service=cron,
)
except ValueError as exc:
console.print(f"[red]Error: {exc}[/red]")
raise typer.Exit(1) from exc
restart_notice = consume_restart_notice_from_env()
if restart_notice and should_show_cli_restart_notice(restart_notice, session_id):
_print_agent_response(
Expand Down
33 changes: 2 additions & 31 deletions nanobot/nanobot.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

from nanobot.agent.hook import AgentHook, SDKCaptureHook
from nanobot.agent.loop import AgentLoop
from nanobot.bus.queue import MessageBus


@dataclass(slots=True)
Expand Down Expand Up @@ -62,31 +61,8 @@ def from_config(
Path(workspace).expanduser().resolve()
)

provider = _make_provider(config)
bus = MessageBus()
defaults = config.agents.defaults

loop = AgentLoop(
bus=bus,
provider=provider,
workspace=config.workspace_path,
model=defaults.model,
max_iterations=defaults.max_tool_iterations,
context_window_tokens=defaults.context_window_tokens,
context_block_limit=defaults.context_block_limit,
max_tool_result_chars=defaults.max_tool_result_chars,
provider_retry_mode=defaults.provider_retry_mode,
tool_hint_max_length=defaults.tool_hint_max_length,
web_config=config.tools.web,
exec_config=config.tools.exec,
restrict_to_workspace=config.tools.restrict_to_workspace,
mcp_servers=config.tools.mcp_servers,
timezone=defaults.timezone,
unified_session=defaults.unified_session,
disabled_skills=defaults.disabled_skills,
session_ttl_minutes=defaults.session_ttl_minutes,
consolidation_ratio=defaults.consolidation_ratio,
tools_config=config.tools,
loop = AgentLoop.from_config(
config,
image_generation_provider_configs={
"openrouter": config.providers.openrouter,
"aihubmix": config.providers.aihubmix,
Expand Down Expand Up @@ -128,8 +104,3 @@ async def run(
)


def _make_provider(config: Any) -> Any:
"""Create the LLM provider from config (extracted from CLI)."""
from nanobot.providers.factory import make_provider

return make_provider(config)
Loading
Loading