diff --git a/frontend/src/pages/AgentsPage.tsx b/frontend/src/pages/AgentsPage.tsx index babe1cca..f78c28d8 100644 --- a/frontend/src/pages/AgentsPage.tsx +++ b/frontend/src/pages/AgentsPage.tsx @@ -1280,7 +1280,7 @@ function InteractTab({ agentId, agentStatus }: { agentId: string; agentStatus: s onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); - handleSend('queued'); + handleSend('immediate'); } }} placeholder="Send a message to this agent..." @@ -1293,23 +1293,8 @@ function InteractTab({ agentId, agentStatus }: { agentId: string; agentStatus: s disabled={sending || !input.trim()} className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm cursor-pointer font-medium" style={{ background: 'var(--color-accent)', color: '#fff', opacity: sending || !input.trim() ? 0.5 : 1 }} - title="Send immediately (interrupts agent)" > - Immediate - - diff --git a/src/openjarvis/agents/executor.py b/src/openjarvis/agents/executor.py index d6f7beb2..f7f1b086 100644 --- a/src/openjarvis/agents/executor.py +++ b/src/openjarvis/agents/executor.py @@ -203,6 +203,12 @@ class AgentExecutor: if not model: raise FatalError("No model configured for agent") + logger.info( + "Agent %s [%s]: using model=%s, engine=%s", + agent["name"], agent["id"], + model, type(engine).__name__, + ) + # Optionally override model via router policy router_policy_key = config.get("router_policy") if router_policy_key and self._system: @@ -247,6 +253,16 @@ class AgentExecutor: input_text = f"{input_text}\n\nNew instructions:\n{user_msgs}" for m in pending: self._manager.mark_message_delivered(m["id"]) + logger.info( + "Agent %s: delivering %d pending message(s)", + agent["name"], len(pending), + ) + else: + logger.info( + "Agent %s: no pending messages, running with " + "instruction only", + agent["name"], + ) # Build AgentContext with memory results from FTS5 backend from openjarvis.agents._stubs import AgentContext @@ -298,7 +314,22 @@ class AgentExecutor: pass # Don't break agent tick if memory retrieval fails agent_ctx.memory_results = memory_results - return agent_instance.run(input_text, context=agent_ctx) + logger.info( + "Agent %s: calling agent.run() with %d chars input", + agent["name"], len(input_text), + ) + _t0 = time.time() + result = agent_instance.run(input_text, context=agent_ctx) + _elapsed = time.time() - _t0 + logger.info( + "Agent %s: agent.run() completed in %.1fs, " + "content_len=%d, turns=%d, tokens=%s", + agent["name"], _elapsed, + len(result.content or ""), + result.turns, + result.metadata.get("total_tokens", "?"), + ) + return result def _build_error_detail(self, error: AgentTickError) -> dict[str, Any]: """Build structured error detail for trace metadata.""" @@ -336,6 +367,12 @@ class AgentExecutor: """Update agent state after tick completion or failure.""" if error is None: # Success + logger.info( + "Tick succeeded for agent %s in %.1fs, " + "response_len=%d", + agent_id, duration, + len(result.content or "") if result else 0, + ) self._manager.end_tick(agent_id) self._manager.update_agent(agent_id, total_runs_increment=1) @@ -381,6 +418,10 @@ class AgentExecutor: "status": "ok", }) elif isinstance(error, EscalateError): + logger.warning( + "Tick escalated for agent %s after %.1fs: %s", + agent_id, duration, error, + ) self._manager.end_tick(agent_id) self._manager.update_agent(agent_id, status="needs_attention") self._bus.publish(EventType.AGENT_TICK_ERROR, { @@ -390,6 +431,10 @@ class AgentExecutor: "duration": duration, }) else: + logger.error( + "Tick failed for agent %s after %.1fs: %s", + agent_id, duration, error, exc_info=error, + ) self._manager.end_tick(agent_id) self._manager.update_agent(agent_id, status="error") # Write error detail to summary_memory so frontend can display it diff --git a/src/openjarvis/server/agent_manager_routes.py b/src/openjarvis/server/agent_manager_routes.py index d92d51ab..f2a03386 100644 --- a/src/openjarvis/server/agent_manager_routes.py +++ b/src/openjarvis/server/agent_manager_routes.py @@ -64,6 +64,24 @@ _BROWSER_SUB_TOOLS = { } +class _LightweightSystem: + """Minimal system facade for the executor — avoids rebuilding the + full JarvisSystem (which picks a random model from Ollama).""" + + def __init__(self, engine: Any, model: str, config: Any = None): + self.engine = engine + self.model = model + self.config = config + self.memory_backend = None + + +def _make_lightweight_system( + engine: Any, model: str, config: Any = None, +) -> _LightweightSystem: + """Build a minimal system from the server's existing state.""" + return _LightweightSystem(engine, model, config) + + def _ensure_registries_populated() -> None: """Ensure ToolRegistry and ChannelRegistry are populated. @@ -452,7 +470,7 @@ def create_agent_manager_router( return {"status": "idle"} @agents_router.post("/{agent_id}/run") - async def run_agent(agent_id: str): + async def run_agent(agent_id: str, request: Request): import threading agent = manager.get_agent(agent_id) @@ -473,28 +491,30 @@ def create_agent_manager_router( status_code=409, detail="Agent is already running" ) + # Re-use the server's engine + model so we don't pick a + # random model from Ollama's list. + server_engine = getattr(request.app.state, "engine", None) + server_model = getattr(request.app.state, "model", "") + server_config = getattr(request.app.state, "config", None) + def _run_tick(): try: from openjarvis.agents.executor import AgentExecutor from openjarvis.core.events import get_event_bus - from openjarvis.system import SystemBuilder executor = AgentExecutor( manager=manager, event_bus=get_event_bus(), ) - try: - system = SystemBuilder().build() - executor.set_system(system) - except Exception as build_err: - manager.end_tick(agent_id) - manager.update_agent(agent_id, status="error") - manager.update_summary_memory( - agent_id, - f"ERROR: Failed to build system: {build_err}", - ) - return + system = _make_lightweight_system( + server_engine, server_model, server_config, + ) + executor.set_system(system) executor.execute_tick(agent_id) except Exception as exc: + logger.error( + "Run-tick failed for agent %s: %s", + agent_id, exc, exc_info=True, + ) try: manager.end_tick(agent_id) except Exception: @@ -603,25 +623,56 @@ def create_agent_manager_router( if not req.stream and req.mode == "immediate": # Non-streaming immediate: trigger a background tick so the # agent processes the message, then return the stored msg. + # Re-use the server's existing system (correct model/engine). import threading + import time as _time from openjarvis.agents.executor import AgentExecutor from openjarvis.core.events import get_event_bus - from openjarvis.system import SystemBuilder + + _srv_engine = getattr(request.app.state, "engine", None) + _srv_model = getattr(request.app.state, "model", "") + _srv_config = getattr(request.app.state, "config", None) def _immediate_tick(): + _start = _time.time() + logger.info( + "Immediate tick starting for agent %s " + "(model=%s)", + agent_id, _srv_model, + ) try: executor = AgentExecutor( manager=manager, event_bus=get_event_bus(), ) - try: - system = SystemBuilder().build() - executor.set_system(system) - except Exception: - return + system = _make_lightweight_system( + _srv_engine, _srv_model, _srv_config, + ) + executor.set_system(system) + logger.info( + "Immediate tick: system ready in %.1fs, " + "executing tick for agent %s", + _time.time() - _start, agent_id, + ) executor.execute_tick(agent_id) - except Exception: - pass + logger.info( + "Immediate tick completed for agent %s " + "in %.1fs", + agent_id, _time.time() - _start, + ) + except Exception as exc: + logger.error( + "Immediate tick failed for agent %s: %s", + agent_id, exc, exc_info=True, + ) + try: + manager.end_tick(agent_id) + except Exception: + pass + manager.update_agent(agent_id, status="error") + manager.update_summary_memory( + agent_id, f"ERROR: {exc}", + ) threading.Thread( target=_immediate_tick, daemon=True,